mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Add batched executor capability discovery (#33852)
## Why Selected capability roots can contribute plugins, MCP servers, connectors, and skills. Discovering each contribution separately requires repeated access to the executor filesystem. ## What changed - Add the `capabilityRoots/discoverV1` exec-server RPC to scan selected roots and materialize recognized plugin manifests, configuration files, skill instructions, and skill metadata in one bounded request. - Add the opt-in `executor_capability_discovery` feature, with a thread-scoped cache and per-step snapshot shared by MCP and skill discovery. - Parse MCP, connector, and skill contributions from the materialized snapshot, including serving cached skill instructions without another filesystem read. ## Testing - Cover discovery limits, manifest precedence, root-local failures, cache reuse, plugin contributions, and parity with the existing environment skill loader. GitOrigin-RevId: f98fd2321cafb58c596db02da1f83c09d8eb375d
This commit is contained in:
1 parent
caa6e554d6
commit
08e30a2e4e
47 files changed
+1957
-47
No files matched your search
@@ -1,6 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_core_skills::loader::EnvironmentSkillMetadata;
|
||||
use codex_core_skills::loader::load_environment_skills_from_discovery;
|
||||
use codex_core_skills::loader::load_environment_skills_from_root;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_protocol::capabilities::CapabilityRootLocation;
|
||||
@@ -44,6 +45,9 @@ impl ExecutorSkillProvider {
|
||||
impl SkillProvider for ExecutorSkillProvider {
|
||||
fn list(&self, query: SkillListQuery) -> SkillProviderFuture<'_, SkillCatalog> {
|
||||
Box::pin(async move {
|
||||
if let Some(discovery) = query.executor_capability_discovery {
|
||||
return Ok(self.list_from_discovery(&discovery));
|
||||
}
|
||||
let mut catalog = SkillCatalog::default();
|
||||
for selected_root in query.executor_roots {
|
||||
let selected_root_id = selected_root.id;
|
||||
@@ -74,6 +78,7 @@ impl SkillProvider for ExecutorSkillProvider {
|
||||
authority.clone(),
|
||||
&selected_root_id,
|
||||
&environment_id,
|
||||
/*instructions*/ None,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -95,6 +100,12 @@ impl SkillProvider for ExecutorSkillProvider {
|
||||
"executor skill resource does not match its package",
|
||||
));
|
||||
}
|
||||
if let Some(contents) = request.resource.environment_contents() {
|
||||
return Ok(SkillReadResult {
|
||||
resource: request.resource.clone(),
|
||||
contents: contents.to_string(),
|
||||
});
|
||||
}
|
||||
let Some((environment_id, resource_path)) = request.resource.environment_path() else {
|
||||
return Err(SkillProviderError::new(
|
||||
"executor skill resource is not bound to an environment",
|
||||
@@ -128,11 +139,50 @@ impl SkillProvider for ExecutorSkillProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutorSkillProvider {
|
||||
fn list_from_discovery(
|
||||
&self,
|
||||
snapshot: &codex_exec_server::ExecutorCapabilityDiscoverySnapshot,
|
||||
) -> SkillCatalog {
|
||||
let mut catalog = SkillCatalog::default();
|
||||
for root in snapshot.roots() {
|
||||
let selected_root_id = &root.selected_root.id;
|
||||
let CapabilityRootLocation::Environment { environment_id, .. } =
|
||||
&root.selected_root.location;
|
||||
let discovery = match &root.result {
|
||||
Ok(discovery) => discovery.as_ref(),
|
||||
Err(error) => {
|
||||
catalog.warnings.push(format!(
|
||||
"Selected capability root `{selected_root_id}` discovery failed: {error}"
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let outcome =
|
||||
load_environment_skills_from_discovery(discovery, self.restriction_product);
|
||||
catalog.warnings.extend(outcome.warnings);
|
||||
let authority =
|
||||
SkillAuthority::new(SkillSourceKind::Executor, selected_root_id.clone());
|
||||
for skill in outcome.skills {
|
||||
catalog.push_entry(catalog_entry_from_skill(
|
||||
&skill.metadata,
|
||||
authority.clone(),
|
||||
selected_root_id,
|
||||
environment_id,
|
||||
Some(skill.instructions),
|
||||
));
|
||||
}
|
||||
}
|
||||
catalog
|
||||
}
|
||||
}
|
||||
|
||||
fn catalog_entry_from_skill(
|
||||
skill: &EnvironmentSkillMetadata,
|
||||
authority: SkillAuthority,
|
||||
selected_root_id: &str,
|
||||
environment_id: &str,
|
||||
instructions: Option<String>,
|
||||
) -> SkillCatalogEntry {
|
||||
let skill_path = skill.path_to_skills_md.inferred_native_path_string();
|
||||
let normalized_path = match skill.path_to_skills_md.infer_path_convention() {
|
||||
@@ -143,16 +193,25 @@ fn catalog_entry_from_skill(
|
||||
"skill://{selected_root_id}/{}",
|
||||
normalized_path.trim_start_matches('/')
|
||||
);
|
||||
let main_prompt = match instructions {
|
||||
Some(contents) => SkillResourceId::environment_with_contents(
|
||||
display_path.clone(),
|
||||
environment_id,
|
||||
skill.path_to_skills_md.clone(),
|
||||
contents,
|
||||
),
|
||||
None => SkillResourceId::environment(
|
||||
display_path.clone(),
|
||||
environment_id,
|
||||
skill.path_to_skills_md.clone(),
|
||||
),
|
||||
};
|
||||
let entry = SkillCatalogEntry::new(
|
||||
SkillPackageId(display_path.clone()),
|
||||
authority,
|
||||
skill.name.clone(),
|
||||
skill.description.clone(),
|
||||
SkillResourceId::environment(
|
||||
display_path.clone(),
|
||||
environment_id,
|
||||
skill.path_to_skills_md.clone(),
|
||||
),
|
||||
main_prompt,
|
||||
)
|
||||
.with_short_description(skill.short_description.clone())
|
||||
.with_display_path(display_path)
|
||||
|
||||
Reference in new issue
Block a user