mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +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
@@ -20,6 +20,7 @@ use super::*;
|
||||
use crate::ExecServerRuntimeOptions;
|
||||
use crate::NoiseChannelIdentity;
|
||||
use crate::ProcessId;
|
||||
use crate::discover_v2::capability_locations::tests as location_fixtures;
|
||||
use crate::noise_relay::stream_handler::NoiseOutboundMessage;
|
||||
use crate::noise_relay::stream_handler::NoiseStreamConnection;
|
||||
use crate::noise_relay::stream_handler::NoiseStreamHandler;
|
||||
@@ -285,14 +286,57 @@ async fn refresh_cancels_old_recovery_and_connects_without_resuming_old_session(
|
||||
#[tokio::test]
|
||||
async fn refresh_retires_a_still_connected_old_executor() -> Result<()> {
|
||||
let _clock = freeze_clock();
|
||||
let directory = tempfile::tempdir()?;
|
||||
let root = directory.path().canonicalize()?;
|
||||
for name in ["old", "new"] {
|
||||
location_fixtures::write(
|
||||
&root.join(name),
|
||||
&format!("codex/skills/{name}/SKILL.md"),
|
||||
&format!("---\nname: {name}\ndescription: {name} executor\n---\n"),
|
||||
)?;
|
||||
}
|
||||
let old = Executor::start(Validator::default()).await?;
|
||||
let new = Executor::start(Validator::default()).await?;
|
||||
old.processor
|
||||
.seed_capability_locations(location_fixtures::request(&root.join("old"))?)
|
||||
.await?;
|
||||
new.processor
|
||||
.seed_capability_locations(location_fixtures::request(&root.join("new"))?)
|
||||
.await?;
|
||||
let (client, registry) = old.client()?;
|
||||
let original = client.get().await?;
|
||||
let environment = Arc::new(crate::Environment::remote_with_client(
|
||||
client.clone(),
|
||||
/*local_runtime_paths*/ None,
|
||||
ScopedSkillsConfig::default(),
|
||||
));
|
||||
let cache = crate::ExecutorCapabilityDiscoveryV2Cache::default();
|
||||
let request = crate::DiscoverV2CapabilitiesRequest {
|
||||
cwd: codex_utils_path_uri::PathUri::from_host_native_path(&root)?,
|
||||
sandbox: None,
|
||||
};
|
||||
let inventory = cache
|
||||
.discover("environment", &environment, request.clone())
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
assert!(inventory.skills.iter().any(|skill| skill.name == "old"));
|
||||
environment.refresh_connection().await?;
|
||||
let reused = cache
|
||||
.discover("environment", &environment, request.clone())
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
assert!(Arc::ptr_eq(&inventory, &reused));
|
||||
*registry.target.lock().unwrap() = new.target.clone();
|
||||
client.refresh_connection().await?;
|
||||
environment.refresh_connection().await?;
|
||||
assert!(original.is_disconnected());
|
||||
assert_ne!(original.session_id(), client.get().await?.session_id());
|
||||
let refreshed = cache
|
||||
.discover("environment", &environment, request)
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
assert!(!Arc::ptr_eq(&inventory, &refreshed));
|
||||
assert!(refreshed.skills.iter().any(|skill| skill.name == "new"));
|
||||
assert!(!refreshed.skills.iter().any(|skill| skill.name == "old"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -375,6 +419,7 @@ async fn registration_renewal_recovers_the_session_and_running_process() -> Resu
|
||||
);
|
||||
let original = client.get().await?;
|
||||
let session_id = original.session_id();
|
||||
assert_eq!(environment.executor_session_id().await?, session_id);
|
||||
let process =
|
||||
original
|
||||
.start_process(
|
||||
@@ -421,6 +466,7 @@ async fn registration_renewal_recovers_the_session_and_running_process() -> Resu
|
||||
let recovered = client.get().await?;
|
||||
assert!(Arc::ptr_eq(&original.inner, &recovered.inner));
|
||||
assert_eq!(recovered.session_id(), session_id);
|
||||
assert_eq!(environment.executor_session_id().await?, session_id);
|
||||
assert_eq!(
|
||||
process.write(b"after-renewal\n".to_vec()).await?.status,
|
||||
crate::protocol::WriteStatus::Accepted
|
||||
|
||||
Reference in New Issue
Block a user