Implement v1 skills extension prompt injection (#26167)

## Why

The skills extension needs a real turn-time path before host, executor,
or remote skills can be routed through it. The previous code was mostly
a placeholder catalog/provider sketch, so there was no bounded
available-skills fragment, no source-owned `SKILL.md` read, and no place
for warnings or per-turn selection state to live.

This PR makes `ext/skills` the authority-preserving flow for listing
candidate skills and injecting only explicitly selected main prompts,
without adding more of that logic to `codex-core`.

## What changed

- Expands catalog entries with `main_prompt`, display path, short
description, dependency metadata, enabled/prompt visibility flags, and
authority/package-aware read requests.
- Replaces the placeholder `providers/*` modules with
`SkillProviderSource` and `SkillProviders`, routing list/read/search
calls by source kind and surfacing provider failures as warnings.
- Adds bounded available-skills rendering and `SKILL.md` main-prompt
truncation before the fragments enter model context.
- Resolves explicit skill selections from structured `UserInput::Skill`,
skill-file mentions, `skill://...` paths, and plain `$skill` text
mentions, then reads selected prompts through their owning provider.
- Stores mutable per-thread skills config and per-turn
catalog/selection/warning state.
- Adds `install_with_providers` so tests and future host wiring can
supply concrete providers.

## Testing

- Not run locally.
- Added `codex-rs/ext/skills/tests/skills_extension.rs` coverage for
available-catalog injection, selected prompt injection through the
owning provider, and prompt-hidden skills that remain invokable.
This commit is contained in:
jif authored and GitHub committed 2026-06-03 16:24:16 +02:00
1 parent c9ae0f48a1
commit 96d2d2f68c
15 files changed
+1001 -335

No files matched your search

+32 -1
View File
@@ -1,6 +1,8 @@
use codex_core::config::Config;
use std::sync::Mutex;
use crate::catalog::SkillCatalog;
use crate::catalog::SkillCatalogEntry;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct SkillsExtensionConfig {
@@ -17,8 +19,37 @@ impl SkillsExtensionConfig {
}
}
#[derive(Debug)]
pub(crate) struct SkillsThreadState {
config: Mutex<SkillsExtensionConfig>,
}
impl SkillsThreadState {
pub(crate) fn new(config: SkillsExtensionConfig) -> Self {
Self {
config: Mutex::new(config),
}
}
pub(crate) fn config(&self) -> SkillsExtensionConfig {
self.config
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
pub(crate) fn set_config(&self, config: SkillsExtensionConfig) {
*self
.config
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = config;
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct SkillsTurnState {
pub(crate) catalog: SkillCatalog,
pub(crate) entrypoints_injected: bool,
pub(crate) selected_entries: Vec<SkillCatalogEntry>,
pub(crate) warnings: Vec<String>,
pub(crate) main_prompts_injected: bool,
}