mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
267eacfca2
## Why
CCA can select a capability root that lives in an executor environment,
but
Codex only had a host-filesystem plugin loader. Before selected executor
plugins can contribute MCP servers, we need a small package boundary
that can
answer:
> Does this selected root contain a plugin, and if so, what does its
manifest
> declare?
The answer must come from the selected environment's filesystem. A
failed
executor lookup must never fall back to the orchestrator filesystem.
## What this changes
This PR introduces:
```rust
PluginProvider::resolve(root)
-> Result<Option<ResolvedPlugin>, Error>
```
`ExecutorPluginProvider` resolves one `SelectedCapabilityRoot` through
its
exact `environment_id`. It checks the recognized manifest locations,
reads the
manifest through that environment's `ExecutorFileSystem`, and returns an
inert
`ResolvedPlugin` containing:
- the opaque selected-root ID;
- the environment-bound plugin root;
- the authority-bound manifest resource;
- parsed metadata and authority-bound component locators.
Descriptor construction rejects manifest or component paths outside the
selected package root, so consumers cannot accidentally lose the package
boundary when they receive a resolved plugin.
If the root has no plugin manifest, resolution returns `None`, allowing
the
caller to treat it as a standalone capability such as a skill.
```text
selected root: repo -> env-1:/workspace/repo
|
| env-1 filesystem only
v
.codex-plugin/plugin.json
|
v
ResolvedPlugin { authority, root, manifest }
```
The existing host loader and the new executor provider now share the
same
manifest parser. Existing `codex-core-plugins::manifest` type paths
remain
available through re-exports, so host behavior and callers are
unchanged.
## Scope
This is intentionally a non-user-visible package-resolution PR. It does
not:
- parse or register plugin MCP server configurations;
- activate skills, connectors, hooks, or MCP servers;
- change app-server wiring;
- introduce host fallback, caching, or lifecycle behavior.
#27670 has merged, and this PR is now based directly on `main`. Together
with
the resolved MCP catalog from #27634, it establishes the inputs needed
for the
executor stdio MCP vertical without changing the existing MCP runtime.
## Follow-up
The next PR will consume `ResolvedPlugin`, read its declared/default MCP
config
through the same executor filesystem, bind supported stdio servers to
that
environment, and feed those registrations into the resolved MCP catalog.
An
app-server E2E will prove that selecting an executor plugin exposes and
invokes
its tool on the owning executor.
Resume/fork semantics, dynamic environment replacement, and non-stdio
placement remain separate lifecycle decisions.
## Validation
- `just fmt`
- `cargo check --tests -p codex-plugin -p codex-core-plugins`
- `just bazel-lock-check`
- `git diff --check`
Test targets were compiled but not executed locally; CI will run the
test and
Clippy suites.
129 lines
4.1 KiB
Rust
129 lines
4.1 KiB
Rust
use crate::manifest::PluginManifest;
|
|
use codex_protocol::capabilities::SelectedCapabilityRoot;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use std::error::Error as StdError;
|
|
use std::future::Future;
|
|
use thiserror::Error;
|
|
|
|
/// A plugin resource paired with the environment that owns its filesystem.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum PluginResourceLocator {
|
|
Environment {
|
|
/// Environment whose filesystem owns the resource.
|
|
environment_id: String,
|
|
/// Absolute resource path within that filesystem.
|
|
path: AbsolutePathBuf,
|
|
},
|
|
}
|
|
|
|
/// Authority-bound location of a resolved plugin package.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum ResolvedPluginLocation {
|
|
Environment {
|
|
/// Environment whose filesystem owns the package.
|
|
environment_id: String,
|
|
/// Absolute package root within that filesystem.
|
|
root: AbsolutePathBuf,
|
|
},
|
|
}
|
|
|
|
/// An inert plugin descriptor whose resources retain their source authority.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct ResolvedPlugin {
|
|
selected_root_id: String,
|
|
location: ResolvedPluginLocation,
|
|
manifest_path: PluginResourceLocator,
|
|
manifest: PluginManifest<PluginResourceLocator>,
|
|
}
|
|
|
|
/// Failure to construct a resolved plugin with internally consistent resources.
|
|
#[derive(Clone, Debug, Error, PartialEq, Eq)]
|
|
pub enum ResolvedPluginError {
|
|
#[error("plugin resource path `{path}` is outside package root `{root}`")]
|
|
ResourceOutsideRoot {
|
|
root: AbsolutePathBuf,
|
|
path: AbsolutePathBuf,
|
|
},
|
|
}
|
|
|
|
impl ResolvedPlugin {
|
|
/// Creates an environment-owned descriptor from a validated plugin manifest.
|
|
pub fn from_environment(
|
|
selected_root_id: String,
|
|
environment_id: String,
|
|
root: AbsolutePathBuf,
|
|
manifest_path: AbsolutePathBuf,
|
|
manifest: PluginManifest<AbsolutePathBuf>,
|
|
) -> Result<Self, ResolvedPluginError> {
|
|
let manifest_path = environment_resource(&environment_id, &root, manifest_path)?;
|
|
let manifest = manifest
|
|
.try_map_resources(|path| environment_resource(&environment_id, &root, path))?;
|
|
Ok(Self {
|
|
selected_root_id,
|
|
location: ResolvedPluginLocation::Environment {
|
|
environment_id,
|
|
root,
|
|
},
|
|
manifest_path,
|
|
manifest,
|
|
})
|
|
}
|
|
|
|
/// Returns the opaque ID supplied for the selected capability root.
|
|
pub fn selected_root_id(&self) -> &str {
|
|
&self.selected_root_id
|
|
}
|
|
|
|
/// Returns the authority-bound package location.
|
|
pub fn location(&self) -> &ResolvedPluginLocation {
|
|
&self.location
|
|
}
|
|
|
|
/// Returns the manifest resource used to resolve this package.
|
|
pub fn manifest_path(&self) -> &PluginResourceLocator {
|
|
&self.manifest_path
|
|
}
|
|
|
|
/// Returns package metadata whose resource fields retain their source authority.
|
|
pub fn manifest(&self) -> &PluginManifest<PluginResourceLocator> {
|
|
&self.manifest
|
|
}
|
|
}
|
|
|
|
fn environment_resource(
|
|
environment_id: &str,
|
|
root: &AbsolutePathBuf,
|
|
path: AbsolutePathBuf,
|
|
) -> Result<PluginResourceLocator, ResolvedPluginError> {
|
|
if !path.as_path().starts_with(root.as_path()) {
|
|
return Err(ResolvedPluginError::ResourceOutsideRoot {
|
|
root: root.clone(),
|
|
path,
|
|
});
|
|
}
|
|
Ok(PluginResourceLocator::Environment {
|
|
environment_id: environment_id.to_string(),
|
|
path,
|
|
})
|
|
}
|
|
|
|
/// Resolves source-owned package roots into inert plugin descriptors.
|
|
///
|
|
/// Implementations must perform all filesystem access through the authority
|
|
/// named by the selected root. `None` means the root contains no plugin
|
|
/// manifest and may be handled as another standalone capability.
|
|
pub trait PluginProvider: Send + Sync {
|
|
/// Source-specific resolution failure.
|
|
type Error: StdError + Send + Sync + 'static;
|
|
|
|
/// Resolves one selected root without activating any of its components.
|
|
fn resolve(
|
|
&self,
|
|
root: &SelectedCapabilityRoot,
|
|
) -> impl Future<Output = Result<Option<ResolvedPlugin>, Self::Error>> + Send;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "provider_tests.rs"]
|
|
mod tests;
|