core: move workspace roots onto environments

This commit is contained in:
pakrym-oai
2026-07-09 19:39:42 -07:00
parent dd51887ce7
commit 014742db1f
47 changed files with 390 additions and 308 deletions
+26 -1
View File
@@ -368,7 +368,6 @@ use codex_core_plugins::remote::RemotePluginShareContext as RemoteCatalogPluginS
use codex_core_plugins::remote::RemotePluginShareSummary as RemoteCatalogPluginShareSummary;
use codex_core_plugins::remote::RemotePluginSummary as RemoteCatalogPluginSummary;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
use codex_exec_server::LOCAL_FS;
use codex_features::FEATURES;
use codex_features::Feature;
@@ -465,6 +464,7 @@ use codex_thread_store::ThreadSortKey as StoreThreadSortKey;
use codex_thread_store::ThreadStore;
use codex_thread_store::ThreadStoreError;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use codex_utils_pty::DEFAULT_OUTPUT_BYTES_CAP;
use std::collections::BTreeMap;
use std::collections::HashMap;
@@ -564,6 +564,7 @@ fn resolve_request_cwd(cwd: Option<PathBuf>) -> Result<Option<AbsolutePathBuf>,
fn resolve_turn_environment_selections(
thread_manager: &ThreadManager,
environments: Option<Vec<TurnEnvironmentParams>>,
fallback_workspace_roots: &[AbsolutePathBuf],
) -> Result<Option<Vec<TurnEnvironmentSelection>>, JSONRPCErrorError> {
let Some(environments) = environments else {
return Ok(None);
@@ -580,9 +581,33 @@ fn resolve_turn_environment_selections(
environment.cwd
))
})?;
let workspace_roots = environment
.runtime_workspace_roots
.map(|roots| {
let mut resolved_roots = Vec::new();
for root in roots {
let root = root.to_inferred_path_uri().ok_or_else(|| {
invalid_request(format!(
"invalid runtime workspace root for environment `{environment_id}`: path `{root}` does not use absolute POSIX or Windows path syntax"
))
})?;
if !resolved_roots.contains(&root) {
resolved_roots.push(root);
}
}
Ok::<_, JSONRPCErrorError>(resolved_roots)
})
.transpose()?
.unwrap_or_else(|| {
fallback_workspace_roots
.iter()
.map(PathUri::from_abs_path)
.collect()
});
selections.push(TurnEnvironmentSelection {
environment_id,
cwd,
workspace_roots,
});
}
thread_manager
@@ -968,8 +968,6 @@ impl ThreadRequestProcessor {
"`permissions` cannot be combined with `sandbox`",
));
}
let environment_selections =
resolve_turn_environment_selections(self.thread_manager.as_ref(), environments)?;
let runtime_workspace_roots = runtime_workspace_roots.map(resolve_runtime_workspace_roots);
let mut typesafe_overrides = self.build_thread_config_overrides(
model,
@@ -1017,7 +1015,7 @@ impl ThreadRequestProcessor {
history_mode.map(Into::into),
session_start_source,
thread_source.map(Into::into),
environment_selections,
environments,
service_name,
allow_provider_model_fallback,
experimental_raw_events,
@@ -1094,7 +1092,7 @@ impl ThreadRequestProcessor {
history_mode: Option<ThreadHistoryMode>,
session_start_source: Option<codex_app_server_protocol::ThreadStartSource>,
thread_source: Option<codex_protocol::protocol::ThreadSource>,
environments: Option<Vec<TurnEnvironmentSelection>>,
environments: Option<Vec<codex_app_server_protocol::TurnEnvironmentParams>>,
service_name: Option<String>,
allow_provider_model_fallback: bool,
experimental_raw_events: bool,
@@ -1188,10 +1186,15 @@ impl ThreadRequestProcessor {
}
}
let environments = environments.unwrap_or_else(|| {
let environments = resolve_turn_environment_selections(
listener_task_context.thread_manager.as_ref(),
environments,
&config.workspace_roots,
)?
.unwrap_or_else(|| {
listener_task_context
.thread_manager
.default_environment_selections(&config.cwd)
.default_environment_selections(&config.cwd, &config.workspace_roots)
});
let dynamic_tools = dynamic_tools.unwrap_or_default();
if !dynamic_tools.is_empty() {
@@ -481,8 +481,18 @@ impl TurnRequestProcessor {
))
})?;
let environment_selections =
resolve_turn_environment_selections(self.thread_manager.as_ref(), params.environments)?;
let runtime_workspace_roots = params
.runtime_workspace_roots
.map(resolve_runtime_workspace_roots);
let fallback_workspace_roots = match runtime_workspace_roots.as_ref() {
Some(workspace_roots) => workspace_roots.clone(),
None => thread.config_snapshot().await.workspace_roots,
};
let environment_selections = resolve_turn_environment_selections(
self.thread_manager.as_ref(),
params.environments,
&fallback_workspace_roots,
)?;
// Map v2 input items to core input items.
let mapped_items: Vec<CoreInputItem> = params
@@ -495,7 +505,12 @@ impl TurnRequestProcessor {
let turn_has_input = !mapped_items.is_empty();
let cwd = resolve_request_cwd(params.cwd)?;
let environments = self
.build_environment_override(thread.as_ref(), cwd, environment_selections)
.build_environment_override(
thread.as_ref(),
cwd,
runtime_workspace_roots.clone(),
environment_selections,
)
.await;
let thread_settings = self
.build_thread_settings_overrides(
@@ -503,7 +518,7 @@ impl TurnRequestProcessor {
ThreadSettingsBuildParams {
method: "turn/start",
environments,
runtime_workspace_roots: params.runtime_workspace_roots,
runtime_workspace_roots,
approval_policy: params.approval_policy,
approvals_reviewer: params.approvals_reviewer,
sandbox_policy: params.sandbox_policy,
@@ -572,33 +587,25 @@ impl TurnRequestProcessor {
&self,
thread: &CodexThread,
cwd: Option<AbsolutePathBuf>,
workspace_roots: Option<Vec<AbsolutePathBuf>>,
environment_selections: Option<Vec<TurnEnvironmentSelection>>,
) -> Option<TurnEnvironmentSelections> {
match (cwd, environment_selections) {
(None, None) => None,
(Some(cwd), None) => {
let environment_selections =
self.thread_manager.default_environment_selections(&cwd);
Some(TurnEnvironmentSelections::new(cwd, environment_selections))
}
(cwd, Some(environment_selections)) => {
let legacy_fallback_cwd = match cwd {
Some(cwd) => cwd,
None => {
let snapshot = thread.config_snapshot().await;
environment_selections
.iter()
.find(|selection| selection.environment_id == LOCAL_ENVIRONMENT_ID)
.and_then(|selection| selection.cwd.to_abs_path().ok())
.unwrap_or_else(|| snapshot.cwd().clone())
}
};
Some(TurnEnvironmentSelections::new(
legacy_fallback_cwd,
environment_selections,
))
}
if cwd.is_none() && workspace_roots.is_none() && environment_selections.is_none() {
return None;
}
let snapshot = thread.config_snapshot().await;
let legacy_fallback_cwd = cwd.unwrap_or_else(|| snapshot.cwd().clone());
let legacy_fallback_workspace_roots = workspace_roots.unwrap_or(snapshot.workspace_roots);
let environment_selections = environment_selections.unwrap_or_else(|| {
self.thread_manager.default_environment_selections(
&legacy_fallback_cwd,
&legacy_fallback_workspace_roots,
)
});
Some(TurnEnvironmentSelections::new(
legacy_fallback_cwd,
environment_selections,
))
}
async fn build_thread_settings_overrides(
@@ -654,8 +661,7 @@ impl TurnRequestProcessor {
|| collaboration_mode.is_some()
|| personality.is_some();
let runtime_workspace_roots =
runtime_workspace_roots_request.map(resolve_runtime_workspace_roots);
let runtime_workspace_roots = runtime_workspace_roots_request;
let approval_policy =
approval_policy.map(codex_app_server_protocol::AskForApproval::to_core);
let approvals_reviewer =
@@ -715,7 +721,6 @@ impl TurnRequestProcessor {
thread
.preview_thread_settings_overrides(CodexThreadSettingsOverrides {
environments: environments.clone(),
workspace_roots: runtime_workspace_roots.clone(),
approval_policy,
approvals_reviewer,
sandbox_policy: sandbox_policy.clone(),
@@ -738,7 +743,6 @@ impl TurnRequestProcessor {
Ok(codex_protocol::protocol::ThreadSettingsOverrides {
environments,
workspace_roots: runtime_workspace_roots,
profile_workspace_roots,
approval_policy,
approvals_reviewer,
@@ -763,7 +767,12 @@ impl TurnRequestProcessor {
let (_, thread) = self.load_thread(&params.thread_id).await?;
let cwd = resolve_request_cwd(params.cwd)?;
let environments = self
.build_environment_override(thread.as_ref(), cwd, /*environment_selections*/ None)
.build_environment_override(
thread.as_ref(),
cwd,
/*workspace_roots*/ None,
/*environment_selections*/ None,
)
.await;
let thread_settings = self
.build_thread_settings_overrides(