mirror of
https://github.com/openai/codex.git
synced 2026-11-07 08:34:29 +00:00
## Why #29856 already owns the durable thread intent and exact environment binding. This PR adds only the small missing extension boundary: an extension can contribute one named World State section, while core still owns persistence, diffing, and model-visible fragment types. This lets skills stay in the skills extension instead of moving their runtime into core. ## Shape ```text extension-owned state | | contribute section id + JSON snapshot + renderer v core World State | | compare with the previous snapshot v no message, or one incremental model-visible update ``` The extension API is deliberately small: ```rust fn contribute_world_state(...) -> Vec<WorldStateSectionContribution> ``` Core adapts the rendered result to `ContextualUserFragment`, records the snapshot, and keeps the existing compaction/resume behavior. ## What changes - Adds extension-owned World State section contributions. - Calls those contributors from the existing per-step World State builder. - Restores durable selected capability roots into extension thread state on resume. - Keeps the actual model-context fragment and rollout machinery in core. ## What does not change - No skill or MCP implementation moves out of its extension. - No new file watcher, generation, or RPC. - No generic migration of existing World State sections. - No change to the stable environment-ID assumption from #29856. ## Example ```text step 1 snapshot: skills = [] step 2 snapshot: skills = [executor-demo:deploy] core asks the skills extension to render only that change. ``` ## Stack 1. **This PR:** let extensions contribute World State sections. 2. Project executor skills through the skills extension. 3. Pin one MCP runtime to each model step. 4. Project selected MCP/app/connector metadata by environment availability. 5. One end-to-end integration scenario.
123 lines
3.1 KiB
Rust
123 lines
3.1 KiB
Rust
use std::sync::Arc;
|
|
|
|
use codex_protocol::ThreadId;
|
|
use codex_protocol::protocol::TurnEnvironmentSelection;
|
|
use serde_json::Value;
|
|
|
|
use crate::ExtensionData;
|
|
|
|
/// Host state available while an extension contributes one sampling step's World State.
|
|
pub struct WorldStateContributionInput<'a> {
|
|
pub thread_id: ThreadId,
|
|
pub turn_id: &'a str,
|
|
pub environments: &'a [TurnEnvironmentSelection],
|
|
pub session_store: &'a ExtensionData,
|
|
pub thread_store: &'a ExtensionData,
|
|
pub turn_store: &'a ExtensionData,
|
|
}
|
|
|
|
/// What the harness knows about the previous value of one extension-owned section.
|
|
pub enum PreviousWorldStateSection<'a> {
|
|
Absent,
|
|
Unknown,
|
|
Known(&'a Value),
|
|
}
|
|
|
|
/// Plain model-visible data rendered by an extension-owned World State section.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct RenderedWorldStateFragment {
|
|
role: &'static str,
|
|
markers: (&'static str, &'static str),
|
|
body: String,
|
|
}
|
|
|
|
impl RenderedWorldStateFragment {
|
|
pub fn new(
|
|
role: &'static str,
|
|
markers: (&'static str, &'static str),
|
|
body: impl Into<String>,
|
|
) -> Self {
|
|
Self {
|
|
role,
|
|
markers,
|
|
body: body.into(),
|
|
}
|
|
}
|
|
|
|
pub fn role(&self) -> &'static str {
|
|
self.role
|
|
}
|
|
|
|
pub fn markers(&self) -> (&'static str, &'static str) {
|
|
self.markers
|
|
}
|
|
|
|
pub fn body(&self) -> &str {
|
|
&self.body
|
|
}
|
|
}
|
|
|
|
type RenderDiff = dyn for<'a> Fn(PreviousWorldStateSection<'a>) -> Option<RenderedWorldStateFragment>
|
|
+ Send
|
|
+ Sync;
|
|
type LegacyFragmentMatcher = dyn Fn(&str, &str) -> bool + Send + Sync;
|
|
|
|
/// One extension-owned World State section captured for a sampling step.
|
|
///
|
|
/// The extension owns the stable ID, comparison snapshot, and diff rendering. The harness owns
|
|
/// persistence and the concrete model-context fragment envelope.
|
|
#[derive(Clone)]
|
|
pub struct WorldStateSectionContribution {
|
|
id: &'static str,
|
|
snapshot: Value,
|
|
render_diff: Arc<RenderDiff>,
|
|
matches_legacy_fragment: Arc<LegacyFragmentMatcher>,
|
|
}
|
|
|
|
impl WorldStateSectionContribution {
|
|
pub fn new(
|
|
id: &'static str,
|
|
snapshot: Value,
|
|
render_diff: impl for<'a> Fn(
|
|
PreviousWorldStateSection<'a>,
|
|
) -> Option<RenderedWorldStateFragment>
|
|
+ Send
|
|
+ Sync
|
|
+ 'static,
|
|
) -> Self {
|
|
Self {
|
|
id,
|
|
snapshot,
|
|
render_diff: Arc::new(render_diff),
|
|
matches_legacy_fragment: Arc::new(|_, _| false),
|
|
}
|
|
}
|
|
|
|
pub fn with_legacy_matcher(
|
|
mut self,
|
|
matcher: impl Fn(&str, &str) -> bool + Send + Sync + 'static,
|
|
) -> Self {
|
|
self.matches_legacy_fragment = Arc::new(matcher);
|
|
self
|
|
}
|
|
|
|
pub fn id(&self) -> &'static str {
|
|
self.id
|
|
}
|
|
|
|
pub fn snapshot(&self) -> &Value {
|
|
&self.snapshot
|
|
}
|
|
|
|
pub fn render_diff(
|
|
&self,
|
|
previous: PreviousWorldStateSection<'_>,
|
|
) -> Option<RenderedWorldStateFragment> {
|
|
(self.render_diff)(previous)
|
|
}
|
|
|
|
pub fn matches_legacy_fragment(&self, role: &str, text: &str) -> bool {
|
|
(self.matches_legacy_fragment)(role, text)
|
|
}
|
|
}
|