mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
c3a479620f
## Why **In short:** this PR resolves already-discovered MCP registrations. It does not read selected plugins or discover their MCP servers. The resolved MCP catalog currently builds config and auto-discovered plugin registrations before runtime contributors are applied. A thread-selected plugin needs a distinct precedence tier in that same initial resolution pass: otherwise a disabled lower-precedence winner can leave stale name-level state behind, and the winning MCP tools cannot be attributed to the selected package reliably. This PR adds that catalog boundary before executor discovery is connected. ## What changed - Added an explicit selected-plugin registration tier between auto-discovered plugins and explicit config. - Collected selected-plugin contributions before the initial catalog build, while leaving compatibility and generic extension overlays in their existing runtime phase. - Retained the winning plugin ID and display name directly on plugin-owned catalog registrations. - Derived MCP tool provenance from the winning catalog entry instead of joining against local-only plugin summaries. - Retained the winning selected server's tool approval policy in the running connection manager, so a selected registration cannot inherit approval behavior from a losing local plugin. - Kept remembered approval session-scoped for selected plugins until there is an authority-aware persistence contract; Codex will not write approval back to an unrelated local plugin. - Preserved existing name-level disabled vetoes for discovered plugins and config, while keeping a selected package's own disabled registration scoped to that registration. - Preserved deterministic selection order and existing config, compatibility, and extension precedence. The resulting order is: ```text auto-discovered plugin < selected plugin < explicit config < compatibility registration < extension overlay ``` ## Behavior and scope This is a catalog and provenance change only. No production host contributes selected-plugin MCP registrations yet, so existing local MCP behavior remains unchanged. The stacked follow-up, #27870, installs the executor plugin provider that produces these registrations. App-server activation remains a separate final step. ## Verification Focused tests cover precedence, deterministic selected-plugin conflicts, disabled-veto behavior across catalog phases, managed requirements before selected-plugin resolution, winning-server approval policy, and attribution when local and selected packages share an ID or server name. CI owns execution of the test suite.
260 lines
9.5 KiB
Rust
260 lines
9.5 KiB
Rust
use std::future::Future;
|
|
use std::pin::Pin;
|
|
use std::sync::Arc;
|
|
|
|
use codex_context_fragments::ContextualUserFragment;
|
|
use codex_protocol::items::TurnItem;
|
|
use codex_protocol::protocol::ReviewDecision;
|
|
use codex_protocol::protocol::TokenUsageInfo;
|
|
use codex_tools::ToolCall;
|
|
use codex_tools::ToolExecutor;
|
|
|
|
use crate::ExtensionData;
|
|
|
|
mod mcp;
|
|
mod prompt;
|
|
mod thread_lifecycle;
|
|
mod tool_lifecycle;
|
|
mod turn_input;
|
|
mod turn_lifecycle;
|
|
|
|
pub use mcp::McpServerContribution;
|
|
pub use mcp::McpServerContributionContext;
|
|
pub use prompt::PromptFragment;
|
|
pub use prompt::PromptSlot;
|
|
pub use thread_lifecycle::ThreadIdleInput;
|
|
pub use thread_lifecycle::ThreadResumeInput;
|
|
pub use thread_lifecycle::ThreadStartInput;
|
|
pub use thread_lifecycle::ThreadStopInput;
|
|
pub use tool_lifecycle::ToolCallOutcome;
|
|
pub use tool_lifecycle::ToolCallSource;
|
|
pub use tool_lifecycle::ToolFinishInput;
|
|
pub use tool_lifecycle::ToolLifecycleFuture;
|
|
pub use tool_lifecycle::ToolStartInput;
|
|
pub use turn_input::TurnInputContext;
|
|
pub use turn_input::TurnInputEnvironment;
|
|
pub use turn_lifecycle::TurnAbortInput;
|
|
pub use turn_lifecycle::TurnErrorInput;
|
|
pub use turn_lifecycle::TurnStartInput;
|
|
pub use turn_lifecycle::TurnStopInput;
|
|
|
|
/// Boxed, sendable future returned by asynchronous extension contributors.
|
|
pub type ExtensionFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
|
|
|
/// Extension contribution that resolves runtime MCP servers from host config.
|
|
///
|
|
/// Contributors run in registration order. Later contributions for the same
|
|
/// name replace earlier ones. Implementations must contribute only names they
|
|
/// own and must apply any source-specific policy before returning a server.
|
|
/// Thread-scoped resolution exposes the host-seeded thread inputs; global
|
|
/// resolution exposes none and must not imply a local fallback. Thread inputs
|
|
/// are frozen for the runtime and do not include lifecycle-contributor state.
|
|
/// Auto-discovered plugin servers are resolved by the plugin manager. A
|
|
/// thread-selected plugin contribution must carry its own package provenance.
|
|
pub trait McpServerContributor<C: Sync>: Send + Sync {
|
|
/// Stable identity used for registration provenance and conflict diagnostics.
|
|
fn id(&self) -> &'static str;
|
|
|
|
fn contribute<'a>(
|
|
&'a self,
|
|
context: McpServerContributionContext<'a, C>,
|
|
) -> ExtensionFuture<'a, Vec<McpServerContribution>>;
|
|
}
|
|
|
|
/// Extension contribution that adds prompt fragments during prompt assembly.
|
|
pub trait ContextContributor: Send + Sync {
|
|
fn contribute<'a>(
|
|
&'a self,
|
|
session_store: &'a ExtensionData,
|
|
thread_store: &'a ExtensionData,
|
|
) -> ExtensionFuture<'a, Vec<PromptFragment>>;
|
|
}
|
|
|
|
/// Contributor for host-owned thread lifecycle gates.
|
|
///
|
|
/// Implementations should use these callbacks to seed, rehydrate, or flush
|
|
/// extension-private thread state. Heavy dependencies belong on the extension
|
|
/// value created by the host, not in these inputs.
|
|
pub trait ThreadLifecycleContributor<C: Sync>: Send + Sync {
|
|
/// Called after host startup has initialized the thread-scoped store.
|
|
fn on_thread_start<'a>(&'a self, input: ThreadStartInput<'a, C>) -> ExtensionFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let _self = self;
|
|
let _input = input;
|
|
})
|
|
}
|
|
|
|
/// Called after the host constructs a runtime from persisted history.
|
|
fn on_thread_resume<'a>(&'a self, input: ThreadResumeInput<'a>) -> ExtensionFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let _self = self;
|
|
let _input = input;
|
|
})
|
|
}
|
|
|
|
/// Called after the host has drained immediately pending thread work.
|
|
///
|
|
/// Implementations may use host capabilities captured by the extension to
|
|
/// submit follow-up input. The host remains responsible for deciding
|
|
/// whether that input starts a turn, is queued, or is ignored.
|
|
fn on_thread_idle<'a>(&'a self, input: ThreadIdleInput<'a>) -> ExtensionFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let _self = self;
|
|
let _input = input;
|
|
})
|
|
}
|
|
|
|
/// Called before the host drops the thread runtime and thread-scoped store.
|
|
fn on_thread_stop<'a>(&'a self, input: ThreadStopInput<'a>) -> ExtensionFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let _self = self;
|
|
let _input = input;
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Contributor for host-owned turn lifecycle gates.
|
|
///
|
|
/// Implementations should use these callbacks to seed, observe, or clear
|
|
/// extension-private turn state. The host exposes stable identifiers and
|
|
/// extension stores instead of core runtime objects.
|
|
pub trait TurnLifecycleContributor: Send + Sync {
|
|
/// Called after turn-scoped extension stores are created, before the task
|
|
/// for the turn starts running.
|
|
fn on_turn_start<'a>(&'a self, input: TurnStartInput<'a>) -> ExtensionFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let _self = self;
|
|
let _input = input;
|
|
})
|
|
}
|
|
|
|
/// Called before the host drops the completed turn runtime and turn store.
|
|
fn on_turn_stop<'a>(&'a self, input: TurnStopInput<'a>) -> ExtensionFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let _self = self;
|
|
let _input = input;
|
|
})
|
|
}
|
|
|
|
/// Called after the host aborts a running turn.
|
|
fn on_turn_abort<'a>(&'a self, input: TurnAbortInput<'a>) -> ExtensionFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let _self = self;
|
|
let _input = input;
|
|
})
|
|
}
|
|
|
|
/// Called when the host observes an error for a running turn.
|
|
fn on_turn_error<'a>(&'a self, input: TurnErrorInput<'a>) -> ExtensionFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let _self = self;
|
|
let _input = input;
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Extension contribution that can add turn-local model input.
|
|
///
|
|
/// Implementations should resolve only the model-visible input they own and
|
|
/// must preserve authority boundaries for external resources. Expensive or
|
|
/// host-specific dependencies belong on the extension value installed by the
|
|
/// host, not in this input.
|
|
pub trait TurnInputContributor: Send + Sync {
|
|
/// Returns additional contextual fragments for one submitted turn.
|
|
fn contribute<'a>(
|
|
&'a self,
|
|
input: TurnInputContext,
|
|
session_store: &'a ExtensionData,
|
|
thread_store: &'a ExtensionData,
|
|
turn_store: &'a ExtensionData,
|
|
) -> ExtensionFuture<'a, Vec<Box<dyn ContextualUserFragment + Send>>>;
|
|
}
|
|
|
|
/// Contributor for host-owned configuration changes.
|
|
///
|
|
/// Implementations should treat the supplied values as immutable before/after
|
|
/// snapshots of the effective thread configuration.
|
|
pub trait ConfigContributor<C>: Send + Sync {
|
|
/// Called after the host commits a changed thread configuration.
|
|
fn on_config_changed(
|
|
&self,
|
|
_session_store: &ExtensionData,
|
|
_thread_store: &ExtensionData,
|
|
_previous_config: &C,
|
|
_new_config: &C,
|
|
) {
|
|
}
|
|
}
|
|
|
|
/// Contributor for token usage checkpoints reported by the model provider.
|
|
///
|
|
/// Implementations should keep this callback cheap. The host calls it after
|
|
/// updating cached token usage and before emitting the corresponding client
|
|
/// token-count notification.
|
|
pub trait TokenUsageContributor: Send + Sync {
|
|
/// Called each time the host records token usage from a model response.
|
|
fn on_token_usage<'a>(
|
|
&'a self,
|
|
_session_store: &'a ExtensionData,
|
|
_thread_store: &'a ExtensionData,
|
|
_turn_store: &'a ExtensionData,
|
|
_token_usage: &'a TokenUsageInfo,
|
|
) -> ExtensionFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let _self = self;
|
|
let _inputs = (_session_store, _thread_store, _turn_store, _token_usage);
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Extension contribution that exposes native tools owned by a feature.
|
|
pub trait ToolContributor: Send + Sync {
|
|
/// Returns the native tools visible for the supplied extension stores.
|
|
fn tools(
|
|
&self,
|
|
session_store: &ExtensionData,
|
|
thread_store: &ExtensionData,
|
|
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>>;
|
|
}
|
|
|
|
/// Contributor for host-owned tool lifecycle gates.
|
|
///
|
|
/// Implementations should use these callbacks to observe tool execution without
|
|
/// inspecting or rewriting tool input/output. Use `ToolContributor` for owning a
|
|
/// tool implementation and hooks for policy that needs tool payloads.
|
|
pub trait ToolLifecycleContributor: Send + Sync {
|
|
/// Called once the host has accepted a tool call for execution.
|
|
fn on_tool_start<'a>(&'a self, _input: ToolStartInput<'a>) -> ToolLifecycleFuture<'a> {
|
|
Box::pin(std::future::ready(()))
|
|
}
|
|
|
|
/// Called after the tool call returns, is blocked, fails, or is cancelled.
|
|
fn on_tool_finish<'a>(&'a self, _input: ToolFinishInput<'a>) -> ToolLifecycleFuture<'a> {
|
|
Box::pin(std::future::ready(()))
|
|
}
|
|
}
|
|
|
|
/// Extension contribution that can claim rendered approval-review prompts.
|
|
pub trait ApprovalReviewContributor: Send + Sync {
|
|
fn contribute<'a>(
|
|
&'a self,
|
|
session_store: &'a ExtensionData,
|
|
thread_store: &'a ExtensionData,
|
|
prompt: &'a str,
|
|
) -> ExtensionFuture<'a, Option<ReviewDecision>>;
|
|
}
|
|
|
|
/// Ordered post-processing contribution for one parsed turn item.
|
|
///
|
|
/// Implementations may mutate the item before it is emitted and may use the
|
|
/// explicitly exposed thread- and turn-lifetime stores when they need durable
|
|
/// extension-private state.
|
|
pub trait TurnItemContributor: Send + Sync {
|
|
fn contribute<'a>(
|
|
&'a self,
|
|
thread_store: &'a ExtensionData,
|
|
turn_store: &'a ExtensionData,
|
|
item: &'a mut TurnItem,
|
|
) -> ExtensionFuture<'a, Result<(), String>>;
|
|
}
|