mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
Acquire and cache opt-in executor capability inventories via V2 (#53736)
## What changed - Add the disabled-by-default `executor_capability_discovery_v2` feature to acquire `capabilities/discoverV2` inventories from supporting remote executors using each selected environment's working directory and sandbox. - Share snapshots with MCP and world-state contributors and skill providers without activating discovered plugins or publishing discovered skills to the model. - Cache inventories by environment instance, executor session, working directory, and sandbox. Coalesce concurrent requests, refresh the cache at turn start, retry transient failures, and refresh MCP projections after recovery. - Resolve the system skills root in the executor and pass it explicitly to capability location discovery. ## Testing Add coverage for feature gating, unsupported executors, inert inventories, cache reuse and eviction, turn refreshes, failure recovery, executor replacement, and system skill locations. GitOrigin-RevId: 1bdca28fa7f17622b27e2713ab3dcc0c13426889
This commit is contained in:
committed by
copyberry
parent
4d82d36ccf
commit
cf0ed4c600
@@ -3,8 +3,11 @@ use super::*;
|
||||
use crate::environment_selection::TurnEnvironmentSnapshot;
|
||||
use crate::environment_selection::combine_selected_capability_roots;
|
||||
use crate::mcp_tool_call::McpToolApprovalContext;
|
||||
use codex_exec_server::DiscoverV2CapabilitiesRequest;
|
||||
use codex_exec_server::ExecutorCapabilityDiscoveryCache;
|
||||
use codex_exec_server::ExecutorCapabilityDiscoverySnapshot;
|
||||
use codex_exec_server::ExecutorCapabilityDiscoveryV2Cache;
|
||||
use codex_exec_server::ExecutorCapabilityDiscoveryV2Snapshot;
|
||||
use codex_exec_server::MAX_SELECTED_CAPABILITY_ROOTS;
|
||||
use codex_exec_server::ResolvedSelectedCapabilityRoot;
|
||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
@@ -122,6 +125,9 @@ impl Session {
|
||||
.await;
|
||||
let ready_selected_capability_roots =
|
||||
Self::ready_selected_capability_roots(&selected_capability_roots);
|
||||
let executor_capability_discovery_v2 = self
|
||||
.executor_capability_discovery_for_step_v2(config, &environments)
|
||||
.await;
|
||||
let executor_capability_discovery = self
|
||||
.executor_capability_discovery_for_step(
|
||||
config,
|
||||
@@ -147,6 +153,7 @@ impl Session {
|
||||
},
|
||||
&ready_selected_capability_roots,
|
||||
executor_capability_discovery.as_deref(),
|
||||
executor_capability_discovery_v2.as_deref(),
|
||||
)
|
||||
.await;
|
||||
let mcp_config = self
|
||||
@@ -229,6 +236,9 @@ impl Session {
|
||||
.await;
|
||||
let ready_selected_capability_roots =
|
||||
Self::ready_selected_capability_roots(&selected_capability_roots);
|
||||
let executor_capability_discovery_v2 = self
|
||||
.executor_capability_discovery_for_step_v2(&desired.config, &desired.environments)
|
||||
.await;
|
||||
let executor_capability_discovery = self
|
||||
.executor_capability_discovery_for_step(
|
||||
&desired.config,
|
||||
@@ -255,6 +265,7 @@ impl Session {
|
||||
},
|
||||
&ready_selected_capability_roots,
|
||||
executor_capability_discovery.as_deref(),
|
||||
executor_capability_discovery_v2.as_deref(),
|
||||
)
|
||||
.await;
|
||||
self.publish_mcp_runtime(
|
||||
@@ -302,6 +313,9 @@ impl Session {
|
||||
.await;
|
||||
let ready_selected_capability_roots =
|
||||
Self::ready_selected_capability_roots(&selected_capability_roots);
|
||||
let executor_capability_discovery_v2 = self
|
||||
.executor_capability_discovery_for_step_v2(&desired.config, &desired.environments)
|
||||
.await;
|
||||
let executor_capability_discovery = self
|
||||
.executor_capability_discovery_for_step(
|
||||
&desired.config,
|
||||
@@ -328,6 +342,7 @@ impl Session {
|
||||
},
|
||||
&ready_selected_capability_roots,
|
||||
executor_capability_discovery.as_deref(),
|
||||
executor_capability_discovery_v2.as_deref(),
|
||||
)
|
||||
.await;
|
||||
let mcp_projection = self
|
||||
@@ -499,6 +514,66 @@ impl Session {
|
||||
Some(Arc::new(discovery))
|
||||
}
|
||||
|
||||
/// Acquires V2 inventories from the thread's current cache.
|
||||
pub(super) async fn executor_capability_discovery_for_step_v2(
|
||||
&self,
|
||||
config: &Config,
|
||||
environments: &TurnEnvironmentSnapshot,
|
||||
) -> Option<Vec<ExecutorCapabilityDiscoveryV2Snapshot>> {
|
||||
if !config
|
||||
.features
|
||||
.enabled(Feature::ExecutorCapabilityDiscoveryV2)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
// Retain this cache if a new turn replaces the thread's current cache.
|
||||
let cache = self
|
||||
.services
|
||||
.thread_extension_data
|
||||
.get_or_init(ExecutorCapabilityDiscoveryV2Cache::default);
|
||||
let remote_environment_count = environments
|
||||
.turn_environments()
|
||||
.filter(|selected| selected.environment.is_remote())
|
||||
.count();
|
||||
cache.ensure_capacity(remote_environment_count);
|
||||
let mut snapshots = Vec::new();
|
||||
for selected in environments.turn_environments() {
|
||||
if !selected.environment.is_remote() {
|
||||
continue;
|
||||
}
|
||||
let environment_id = &selected.selection.environment_id;
|
||||
let info = match selected.environment.info().await {
|
||||
Ok(info) => info,
|
||||
Err(_) => {
|
||||
tracing::warn!(
|
||||
environment_id,
|
||||
"could not read executor discovery V2 support"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if !info.capabilities.capability_discovery_v2 {
|
||||
continue;
|
||||
}
|
||||
let request = DiscoverV2CapabilitiesRequest {
|
||||
cwd: selected.cwd().clone(),
|
||||
sandbox: Some(selected.sandbox_context(/*additional_permissions*/ None)),
|
||||
};
|
||||
let result = cache
|
||||
.discover(environment_id, &selected.environment, request)
|
||||
.await;
|
||||
snapshots.push(ExecutorCapabilityDiscoveryV2Snapshot {
|
||||
environment_id: environment_id.clone(),
|
||||
result,
|
||||
});
|
||||
}
|
||||
if cache.take_recovered_discovery() {
|
||||
// Refresh MCP after discovery recovers.
|
||||
self.mark_mcp_runtime_dirty();
|
||||
}
|
||||
Some(snapshots)
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_selected_capability_roots_for_step(
|
||||
&self,
|
||||
environments: &TurnEnvironmentSnapshot,
|
||||
@@ -715,6 +790,9 @@ impl Session {
|
||||
let environments = self.services.turn_environments.snapshot().await;
|
||||
let environment_selections = environments.all_selections();
|
||||
let desired = self.latest_mcp_desired_state(auth, environments).await;
|
||||
let executor_capability_discovery_v2 = self
|
||||
.executor_capability_discovery_for_step_v2(&desired.config, &desired.environments)
|
||||
.await;
|
||||
let executor_capability_discovery = self
|
||||
.executor_capability_discovery_for_step(
|
||||
&desired.config,
|
||||
@@ -741,6 +819,7 @@ impl Session {
|
||||
},
|
||||
&ready_selected_capability_roots,
|
||||
executor_capability_discovery.as_deref(),
|
||||
executor_capability_discovery_v2.as_deref(),
|
||||
)
|
||||
.await;
|
||||
self.publish_mcp_runtime(
|
||||
|
||||
@@ -3923,6 +3923,9 @@ impl Session {
|
||||
.await;
|
||||
let ready_selected_capability_roots =
|
||||
Self::ready_selected_capability_roots(&selected_capability_roots);
|
||||
let executor_capability_discovery_v2 = self
|
||||
.executor_capability_discovery_for_step_v2(&turn_context.config, &environments)
|
||||
.await;
|
||||
let executor_capability_discovery = self
|
||||
.executor_capability_discovery_for_step(
|
||||
&turn_context.config,
|
||||
@@ -3932,6 +3935,9 @@ impl Session {
|
||||
.await;
|
||||
let extension_data =
|
||||
codex_extension_api::ExtensionData::new(turn_context.sub_id.clone());
|
||||
if let Some(snapshots) = &executor_capability_discovery_v2 {
|
||||
extension_data.insert(snapshots.clone());
|
||||
}
|
||||
if let Some(messages) = settings
|
||||
.model_info
|
||||
.model_messages
|
||||
@@ -3969,6 +3975,7 @@ impl Session {
|
||||
&turn_context.originator,
|
||||
&ready_selected_capability_roots,
|
||||
executor_capability_discovery.as_deref(),
|
||||
executor_capability_discovery_v2.as_deref(),
|
||||
)
|
||||
.with_session_source(&turn_context.session_source)
|
||||
.with_selected_environments(&environments.all_selections()),
|
||||
@@ -3990,6 +3997,7 @@ impl Session {
|
||||
Ok::<_, CodexErr>((
|
||||
selected_capability_roots,
|
||||
executor_capability_discovery,
|
||||
executor_capability_discovery_v2,
|
||||
mcp,
|
||||
tool_router,
|
||||
extension_data,
|
||||
@@ -4005,6 +4013,7 @@ impl Session {
|
||||
let (
|
||||
selected_capability_roots,
|
||||
executor_capability_discovery,
|
||||
executor_capability_discovery_v2,
|
||||
mcp,
|
||||
tool_router,
|
||||
extension_data,
|
||||
@@ -4024,6 +4033,7 @@ impl Session {
|
||||
selected_capability_roots,
|
||||
executor_capability_discovery,
|
||||
extension_data,
|
||||
executor_capability_discovery_v2,
|
||||
mcp,
|
||||
tool_router,
|
||||
loaded_agents_md,
|
||||
|
||||
@@ -1208,6 +1208,7 @@ impl Session {
|
||||
},
|
||||
/*ready_selected_capability_roots*/ &[],
|
||||
/*executor_capability_discovery*/ None,
|
||||
/*executor_capability_discovery_v2*/ None,
|
||||
)
|
||||
.await;
|
||||
(auth, mcp_projection)
|
||||
@@ -1931,6 +1932,7 @@ impl Session {
|
||||
},
|
||||
/*ready_selected_capability_roots*/ &[],
|
||||
/*executor_capability_discovery*/ None,
|
||||
/*executor_capability_discovery_v2*/ None,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::session::turn_context::TurnContext;
|
||||
use crate::session::turn_context::TurnEnvironment;
|
||||
use crate::tools::router::ToolRouter;
|
||||
use codex_exec_server::ExecutorCapabilityDiscoverySnapshot;
|
||||
use codex_exec_server::ExecutorCapabilityDiscoveryV2Snapshot;
|
||||
use codex_exec_server::ResolvedSelectedCapabilityRoot;
|
||||
use codex_extension_api::ExtensionData;
|
||||
use codex_file_system::EnvironmentAccess;
|
||||
@@ -40,6 +41,7 @@ pub(crate) struct StepContext {
|
||||
pub(crate) executor_capability_discovery: Option<Arc<ExecutorCapabilityDiscoverySnapshot>>,
|
||||
/// Keeps the extension inputs used to build this step's tools for its World State as well.
|
||||
pub(crate) extension_data: ExtensionData,
|
||||
pub(crate) executor_capability_discovery_v2: Option<Vec<ExecutorCapabilityDiscoveryV2Snapshot>>,
|
||||
/// The exact MCP connections, configuration, and catalog captured for this step.
|
||||
pub(crate) mcp: Arc<McpBinding>,
|
||||
/// The finalized tool plan advertised and executed for this exact sampling request.
|
||||
|
||||
@@ -292,6 +292,7 @@ impl StepContext {
|
||||
selected_capability_roots: Vec::new(),
|
||||
executor_capability_discovery: None,
|
||||
extension_data: codex_extension_api::ExtensionData::new(turn.sub_id.clone()),
|
||||
executor_capability_discovery_v2: None,
|
||||
mcp: Arc::new(codex_mcp::McpBinding::empty(mcp_config_for_test(
|
||||
&turn.config,
|
||||
))),
|
||||
|
||||
@@ -335,6 +335,9 @@ impl Session {
|
||||
for contributor in self.services.extensions.context_contributors() {
|
||||
for section in contributor
|
||||
.contribute_world_state(WorldStateContributionInput {
|
||||
executor_capability_discovery_v2: step_context
|
||||
.executor_capability_discovery_v2
|
||||
.as_deref(),
|
||||
thread_id: self.thread_id(),
|
||||
turn_id: turn_context.sub_id.as_str(),
|
||||
model_info: &step_context.settings.model_info,
|
||||
|
||||
Reference in New Issue
Block a user