mirror of
https://github.com/openai/codex.git
synced 2026-05-23 04:24:21 +00:00
Compare commits
2 Commits
fc/feedbac
...
starr/cca-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19935d9278 | ||
|
|
34404e9633 |
@@ -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,
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
}
|
||||
@@ -219,6 +222,10 @@ impl ConfigManager {
|
||||
typesafe_overrides: ConfigOverrides,
|
||||
fallback_cwd: Option<PathBuf>,
|
||||
) -> std::io::Result<Config> {
|
||||
let file_system = self
|
||||
.runtime_capabilities
|
||||
.require_local_filesystem("load app-server config")
|
||||
.map_err(std::io::Error::other)?;
|
||||
let merged_cli_overrides = cli_overrides
|
||||
.iter()
|
||||
.cloned()
|
||||
@@ -239,7 +246,7 @@ impl ConfigManager {
|
||||
.fallback_cwd(fallback_cwd)
|
||||
.cloud_requirements(self.current_cloud_requirements())
|
||||
.thread_config_loader(self.current_thread_config_loader())
|
||||
.build()
|
||||
.build_with_file_system(file_system.as_ref())
|
||||
.await?;
|
||||
self.apply_runtime_feature_enablement(&mut config);
|
||||
self.apply_arg0_paths(&mut config);
|
||||
@@ -258,8 +265,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 +308,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 +316,7 @@ impl ConfigManager {
|
||||
/*strict_config*/ false,
|
||||
cloud_requirements,
|
||||
Arg0DispatchPaths::default(),
|
||||
Arc::new(RuntimeCapabilities::local(&environment_manager)),
|
||||
Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
|
||||
@@ -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(),
|
||||
@@ -408,6 +413,7 @@ impl MessageProcessor {
|
||||
arg0_paths.clone(),
|
||||
Arc::clone(&config),
|
||||
config_manager.clone(),
|
||||
Arc::clone(&runtime_capabilities),
|
||||
Arc::clone(&thread_store),
|
||||
Arc::clone(&pending_thread_unloads),
|
||||
thread_state_manager.clone(),
|
||||
@@ -461,13 +467,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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(¶ms.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(¶ms.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(
|
||||
¶ms.path,
|
||||
CreateDirectoryOptions {
|
||||
@@ -104,7 +111,7 @@ impl FsRequestProcessor {
|
||||
params: FsGetMetadataParams,
|
||||
) -> Result<FsGetMetadataResponse, JSONRPCErrorError> {
|
||||
let metadata = self
|
||||
.file_system
|
||||
.file_system()?
|
||||
.get_metadata(¶ms.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(¶ms.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(
|
||||
¶ms.path,
|
||||
RemoveOptions {
|
||||
@@ -160,7 +167,7 @@ impl FsRequestProcessor {
|
||||
&self,
|
||||
params: FsCopyParams,
|
||||
) -> Result<FsCopyResponse, JSONRPCErrorError> {
|
||||
self.file_system
|
||||
self.file_system()?
|
||||
.copy(
|
||||
¶ms.source_path,
|
||||
¶ms.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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -330,6 +330,7 @@ pub(crate) struct ThreadRequestProcessor {
|
||||
pub(super) arg0_paths: Arg0DispatchPaths,
|
||||
pub(super) config: Arc<Config>,
|
||||
pub(super) config_manager: ConfigManager,
|
||||
pub(super) runtime_capabilities: Arc<RuntimeCapabilities>,
|
||||
pub(super) thread_store: Arc<dyn ThreadStore>,
|
||||
pub(super) pending_thread_unloads: Arc<Mutex<HashSet<ThreadId>>>,
|
||||
pub(super) thread_state_manager: ThreadStateManager,
|
||||
@@ -350,6 +351,7 @@ impl ThreadRequestProcessor {
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
config: Arc<Config>,
|
||||
config_manager: ConfigManager,
|
||||
runtime_capabilities: Arc<RuntimeCapabilities>,
|
||||
thread_store: Arc<dyn ThreadStore>,
|
||||
pending_thread_unloads: Arc<Mutex<HashSet<ThreadId>>>,
|
||||
thread_state_manager: ThreadStateManager,
|
||||
@@ -366,6 +368,7 @@ impl ThreadRequestProcessor {
|
||||
arg0_paths,
|
||||
config,
|
||||
config_manager,
|
||||
runtime_capabilities,
|
||||
thread_store,
|
||||
pending_thread_unloads,
|
||||
thread_state_manager,
|
||||
@@ -880,12 +883,14 @@ impl ThreadRequestProcessor {
|
||||
};
|
||||
let request_trace = request_context.request_trace();
|
||||
let config_manager = self.config_manager.clone();
|
||||
let runtime_capabilities = Arc::clone(&self.runtime_capabilities);
|
||||
let outgoing = Arc::clone(&listener_task_context.outgoing);
|
||||
let error_request_id = request_id.clone();
|
||||
let thread_start_task = async move {
|
||||
if let Err(error) = Self::thread_start_task(
|
||||
listener_task_context,
|
||||
config_manager,
|
||||
runtime_capabilities,
|
||||
request_id,
|
||||
app_server_client_name,
|
||||
app_server_client_version,
|
||||
@@ -970,6 +975,7 @@ impl ThreadRequestProcessor {
|
||||
async fn thread_start_task(
|
||||
listener_task_context: ListenerTaskContext,
|
||||
config_manager: ConfigManager,
|
||||
runtime_capabilities: Arc<RuntimeCapabilities>,
|
||||
request_id: ConnectionRequestId,
|
||||
app_server_client_name: Option<String>,
|
||||
app_server_client_version: Option<String>,
|
||||
@@ -1006,9 +1012,13 @@ impl ThreadRequestProcessor {
|
||||
&& config.active_project.trust_level.is_none()
|
||||
&& (requested_permissions_trust_project || effective_permissions_trust_project)
|
||||
{
|
||||
let trust_target = resolve_root_git_project_for_trust(LOCAL_FS.as_ref(), &config.cwd)
|
||||
.await
|
||||
.unwrap_or_else(|| config.cwd.clone());
|
||||
let file_system = runtime_capabilities
|
||||
.require_local_filesystem("trust requested thread cwd")
|
||||
.map_err(|err| internal_error(err.to_string()))?;
|
||||
let trust_target =
|
||||
resolve_root_git_project_for_trust(file_system.as_ref(), &config.cwd)
|
||||
.await
|
||||
.unwrap_or_else(|| config.cwd.clone());
|
||||
let current_cli_overrides = config_manager.current_cli_overrides();
|
||||
let cli_overrides_with_trust;
|
||||
let cli_overrides_for_reload = if let Err(err) =
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1069,11 +1069,18 @@ impl ConfigBuilder {
|
||||
}
|
||||
|
||||
pub async fn build(self) -> std::io::Result<Config> {
|
||||
// Keep the large config-loading future off small runtime thread stacks.
|
||||
Box::pin(self.build_inner()).await
|
||||
self.build_with_file_system(LOCAL_FS.as_ref()).await
|
||||
}
|
||||
|
||||
async fn build_inner(self) -> std::io::Result<Config> {
|
||||
pub async fn build_with_file_system(
|
||||
self,
|
||||
file_system: &dyn ExecutorFileSystem,
|
||||
) -> std::io::Result<Config> {
|
||||
// Keep the large config-loading future off small runtime thread stacks.
|
||||
Box::pin(self.build_inner(file_system)).await
|
||||
}
|
||||
|
||||
async fn build_inner(self, file_system: &dyn ExecutorFileSystem) -> std::io::Result<Config> {
|
||||
let Self {
|
||||
codex_home,
|
||||
cli_overrides,
|
||||
@@ -1098,7 +1105,7 @@ impl ConfigBuilder {
|
||||
};
|
||||
harness_overrides.cwd = Some(cwd.to_path_buf());
|
||||
let config_layer_stack = load_config_layers_state(
|
||||
LOCAL_FS.as_ref(),
|
||||
file_system,
|
||||
&codex_home,
|
||||
Some(cwd),
|
||||
&cli_overrides,
|
||||
@@ -1159,7 +1166,7 @@ impl ConfigBuilder {
|
||||
config_layer_stack.requirements_toml().clone(),
|
||||
)?;
|
||||
let mut config = Config::load_config_with_layer_stack(
|
||||
LOCAL_FS.as_ref(),
|
||||
file_system,
|
||||
lock_config_toml,
|
||||
harness_overrides,
|
||||
codex_home,
|
||||
@@ -1173,7 +1180,7 @@ impl ConfigBuilder {
|
||||
return Ok(config);
|
||||
}
|
||||
Config::load_config_with_layer_stack(
|
||||
LOCAL_FS.as_ref(),
|
||||
file_system,
|
||||
config_toml,
|
||||
harness_overrides,
|
||||
codex_home,
|
||||
@@ -2425,10 +2432,9 @@ impl Config {
|
||||
guardian_policy_config_source: _,
|
||||
} = config_layer_stack.requirements().clone();
|
||||
|
||||
let user_instructions =
|
||||
AgentsMdManager::load_global_instructions(LOCAL_FS.as_ref(), Some(&codex_home))
|
||||
.await
|
||||
.map(|loaded| loaded.contents);
|
||||
let user_instructions = AgentsMdManager::load_global_instructions(fs, Some(&codex_home))
|
||||
.await
|
||||
.map(|loaded| loaded.contents);
|
||||
let mut startup_warnings = config_layer_stack
|
||||
.startup_warnings()
|
||||
.unwrap_or_default()
|
||||
|
||||
@@ -13,6 +13,7 @@ mod client_common;
|
||||
mod realtime_context;
|
||||
mod realtime_conversation;
|
||||
mod realtime_prompt;
|
||||
mod runtime_capabilities;
|
||||
pub(crate) mod session;
|
||||
pub use session::SteerInputError;
|
||||
mod codex_thread;
|
||||
@@ -190,6 +191,8 @@ pub use exec_policy::check_execpolicy_for_warnings;
|
||||
pub use exec_policy::format_exec_policy_error_with_source;
|
||||
pub use exec_policy::load_exec_policy;
|
||||
pub use installation_id::resolve_installation_id;
|
||||
pub use runtime_capabilities::RuntimeCapabilities;
|
||||
pub use runtime_capabilities::RuntimeMode;
|
||||
pub use turn_metadata::build_turn_metadata_header;
|
||||
pub mod compact;
|
||||
mod memory_usage;
|
||||
|
||||
181
codex-rs/core/src/runtime_capabilities.rs
Normal file
181
codex-rs/core/src/runtime_capabilities.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_exec_server::Environment;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
|
||||
/// Describes whether a runtime may use ambient worker-local capabilities.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RuntimeMode {
|
||||
LocalCodex,
|
||||
Isolated,
|
||||
}
|
||||
|
||||
/// Ambient worker-local capabilities available to a Codex runtime.
|
||||
///
|
||||
/// V1 intentionally models one worker-local `Environment` capability. Local
|
||||
/// filesystem and local exec access are derived views of that one capability
|
||||
/// rather than independently configurable powers.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RuntimeCapabilities {
|
||||
mode: RuntimeMode,
|
||||
local_environment: Option<Arc<Environment>>,
|
||||
}
|
||||
|
||||
impl RuntimeCapabilities {
|
||||
/// Builds capabilities for a local Codex runtime.
|
||||
pub fn local(environment_manager: &EnvironmentManager) -> Self {
|
||||
Self {
|
||||
mode: RuntimeMode::LocalCodex,
|
||||
local_environment: Some(environment_manager.local_environment()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds capabilities for a runtime isolated from worker-local powers.
|
||||
pub fn isolated() -> Self {
|
||||
Self {
|
||||
mode: RuntimeMode::Isolated,
|
||||
local_environment: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether this runtime is local Codex or isolated.
|
||||
pub fn mode(&self) -> RuntimeMode {
|
||||
self.mode
|
||||
}
|
||||
|
||||
/// Returns the ambient worker-local environment when available.
|
||||
pub fn local_environment(&self) -> Option<Arc<Environment>> {
|
||||
self.local_environment.as_ref().map(Arc::clone)
|
||||
}
|
||||
|
||||
/// Returns the ambient worker-local environment or an unsupported error.
|
||||
pub fn require_local_environment(&self, operation: &str) -> CodexResult<Arc<Environment>> {
|
||||
self.local_environment()
|
||||
.ok_or_else(|| missing_local_capability(operation, "environment"))
|
||||
}
|
||||
|
||||
/// Returns the ambient worker-local filesystem when available.
|
||||
pub fn local_filesystem(&self) -> Option<Arc<dyn ExecutorFileSystem>> {
|
||||
self.local_environment()
|
||||
.map(|environment| environment.get_filesystem())
|
||||
}
|
||||
|
||||
/// Returns the ambient worker-local filesystem or an unsupported error.
|
||||
pub fn require_local_filesystem(
|
||||
&self,
|
||||
operation: &str,
|
||||
) -> CodexResult<Arc<dyn ExecutorFileSystem>> {
|
||||
self.local_filesystem()
|
||||
.ok_or_else(|| missing_local_capability(operation, "filesystem"))
|
||||
}
|
||||
|
||||
/// Returns whether ambient worker-local exec is available.
|
||||
pub fn local_exec_available(&self) -> bool {
|
||||
self.local_environment.is_some()
|
||||
}
|
||||
|
||||
/// Returns the ambient worker-local exec environment when available.
|
||||
pub fn local_exec(&self) -> Option<Arc<Environment>> {
|
||||
self.local_environment()
|
||||
}
|
||||
|
||||
/// Returns the ambient worker-local exec environment or an unsupported error.
|
||||
pub fn require_local_exec(&self, operation: &str) -> CodexResult<Arc<Environment>> {
|
||||
self.local_exec()
|
||||
.ok_or_else(|| missing_local_capability(operation, "exec"))
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_local_capability(operation: &str, capability: &str) -> CodexErr {
|
||||
CodexErr::UnsupportedOperation(format!(
|
||||
"{operation} requires ambient worker-local {capability}"
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::RuntimeCapabilities;
|
||||
use super::RuntimeMode;
|
||||
|
||||
#[tokio::test]
|
||||
async fn local_capabilities_derive_worker_local_views_from_environment() {
|
||||
let environment_manager = EnvironmentManager::default_for_tests();
|
||||
let expected_environment = environment_manager.local_environment();
|
||||
let expected_filesystem = expected_environment.get_filesystem();
|
||||
let capabilities = RuntimeCapabilities::local(&environment_manager);
|
||||
|
||||
assert_eq!(capabilities.mode(), RuntimeMode::LocalCodex);
|
||||
assert!(Arc::ptr_eq(
|
||||
&capabilities.local_environment().expect("local environment"),
|
||||
&expected_environment,
|
||||
));
|
||||
assert!(Arc::ptr_eq(
|
||||
&capabilities
|
||||
.require_local_environment("test operation")
|
||||
.expect("required local environment"),
|
||||
&expected_environment,
|
||||
));
|
||||
assert!(Arc::ptr_eq(
|
||||
&capabilities.local_filesystem().expect("local filesystem"),
|
||||
&expected_filesystem,
|
||||
));
|
||||
assert!(Arc::ptr_eq(
|
||||
&capabilities
|
||||
.require_local_filesystem("test operation")
|
||||
.expect("required local filesystem"),
|
||||
&expected_filesystem,
|
||||
));
|
||||
assert!(capabilities.local_exec_available());
|
||||
assert!(Arc::ptr_eq(
|
||||
&capabilities.local_exec().expect("local exec"),
|
||||
&expected_environment,
|
||||
));
|
||||
assert!(Arc::ptr_eq(
|
||||
&capabilities
|
||||
.require_local_exec("test operation")
|
||||
.expect("local exec"),
|
||||
&expected_environment,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn isolated_capabilities_do_not_expose_worker_local_views() {
|
||||
let capabilities = RuntimeCapabilities::isolated();
|
||||
|
||||
assert_eq!(capabilities.mode(), RuntimeMode::Isolated);
|
||||
assert!(capabilities.local_environment().is_none());
|
||||
assert!(capabilities.local_filesystem().is_none());
|
||||
assert!(!capabilities.local_exec_available());
|
||||
assert!(capabilities.local_exec().is_none());
|
||||
assert_eq!(
|
||||
unsupported_message(capabilities.require_local_environment("test operation")),
|
||||
"test operation requires ambient worker-local environment",
|
||||
);
|
||||
assert_eq!(
|
||||
unsupported_message(capabilities.require_local_filesystem("test operation")),
|
||||
"test operation requires ambient worker-local filesystem",
|
||||
);
|
||||
assert_eq!(
|
||||
unsupported_message(capabilities.require_local_exec("test operation")),
|
||||
"test operation requires ambient worker-local exec",
|
||||
);
|
||||
}
|
||||
|
||||
fn unsupported_message<T>(result: CodexResult<T>) -> String {
|
||||
match result {
|
||||
Ok(_) => panic!("expected unsupported operation"),
|
||||
Err(CodexErr::UnsupportedOperation(message)) => message,
|
||||
Err(err) => panic!("expected unsupported operation, got {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user