Files
codex/codex-rs/exec-server-protocol/src/capabilities.rs
T
Anthony Tafoya ab7439231c Add executor capability discovery V2 infrastructure (#47683)
## What changed

- Define `capabilities/discoverV2` request and inventory types and advertise `capabilityDiscoveryV2` in executor metadata.
- Prewarm installed plugin and global skill locations at server startup, with nonfatal scan failures.
- Add sandbox-scoped discovery caching with file watcher invalidation and request-time reloads when watching is unavailable.
- Batch metadata reads through the filesystem sandbox helper and bound discovery responses to 4 MiB and 2,048 capabilities.
- Share plugin identity and installed-version selection in `codex-core-plugin-common`, and fall back to `.app.json` when a plugin does not declare an apps configuration.

The V2 discovery manager is not yet wired into RPC request dispatch.

## Testing

Add coverage for inventory metadata, sandbox cache isolation, watcher invalidation, startup scan failures, response limits, and compatibility with older executor metadata.

GitOrigin-RevId: a223a5bc15fdf251023c0c71f468fd92c3b2d847
2026-09-23 22:20:34 +00:00

103 lines
3.2 KiB
Rust

//! Executor capability inventory; callers apply identity and enablement.
use codex_file_system::FileSystemSandboxContext;
use codex_utils_path_uri::PathUri;
use serde::Deserialize;
use serde::Serialize;
use std::fmt;
use crate::CapabilityTextFile;
use crate::DiscoveredPluginFiles;
pub const CAPABILITIES_DISCOVER_V2_METHOD: &str = "capabilities/discoverV2";
/// Discovers installed capabilities on the executor.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DiscoverV2CapabilitiesRequest {
pub cwd: PathUri,
#[serde(default)]
pub sandbox: Option<FileSystemSandboxContext>,
}
/// Discovered metadata before caller policy is applied.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DiscoverV2CapabilitiesResponse {
pub plugins: Vec<ExecutorPlugin>,
/// Skills outside installed packages.
pub skills: Vec<ExecutorSkill>,
#[serde(default)]
pub warnings: Vec<String>,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecutorPlugin {
/// Environment-local ID: `<name>@<marketplace>`.
pub id: String,
pub remote_plugin_id: Option<String>,
pub version: String,
pub root: PathUri,
pub files: DiscoveredPluginFiles,
pub skills: Vec<ExecutorSkill>,
}
/// Skill metadata, excluding instructions and resources.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecutorSkill {
pub name: String,
/// Nearest enclosing legacy plugin's name, following V1 precedence.
#[serde(default)]
pub namespace: Option<String>,
pub description: String,
#[serde(default)]
pub short_description: Option<String>,
/// Path to SKILL.md, not its containing directory.
pub path: PathUri,
pub metadata: Option<CapabilityTextFile>,
}
impl fmt::Debug for ExecutorPlugin {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ExecutorPlugin")
.field("id", &self.id)
.field("remote_plugin_id", &self.remote_plugin_id)
.field("version", &self.version)
.field("root", &self.root)
.field("manifest_path", &self.files.manifest.path)
.field(
"mcp_config_path",
&self.files.mcp_config.as_ref().map(|file| &file.path),
)
.field(
"apps_config_path",
&self.files.apps_config.as_ref().map(|file| &file.path),
)
.field("skills", &self.skills)
.finish()
}
}
impl fmt::Debug for ExecutorSkill {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
// Omit potentially sensitive descriptions and file contents.
formatter
.debug_struct("ExecutorSkill")
.field("name", &self.name)
.field("namespace", &self.namespace)
.field("path", &self.path)
.field(
"metadata_path",
&self.metadata.as_ref().map(|file| &file.path),
)
.finish()
}
}
#[cfg(test)]
#[path = "capabilities_tests.rs"]
mod tests;