mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
a287c5dffd
## Why #27198 made the extension-owned `codex_apps` MCP connection the hosted plugin runtime, but its `mcp/skill` resources still bypassed the skills extension. App-server could list and read those resources through generic MCP APIs, but a thread with no selected environment did not expose them in the model's skills catalog or load their `SKILL.md` through `$skill`. Hosted skills should stay remote while using the same typed catalog, source authority, deduplication, bounded contextual catalog, and selected-skill prompt injection as host and executor skills. They should not be downloaded or exposed as ambient filesystem paths. ## What changed - Add a session-scoped `McpResourceClient` over the replaceable MCP connection manager so resource list/read calls follow startup and refresh replacements. - Add a `BackendSkillProvider` that pages `codex_apps` resources, accepts bounded and validated `mcp/skill` entries, and reads a selected skill's `SKILL.md` through the same MCP connection. - Register the remote provider in app-server and include it in the skills catalog even when a thread has no selected capability roots or executor. - Contribute hosted skill metadata through the bounded `AvailableSkillsInstructions` developer-context path, exclude remote entries from per-turn catalog injection, and classify `<skills>` messages as contextual developer content so rollback can trim and rebuild them correctly. ## Testing - Extend the app-server MCP resource integration test with `environments: []` to exercise two-page discovery, filter a non-`mcp/skill` resource, verify the escaped developer catalog entry and user-role `<skill>` fragment containing the fetched `SKILL.md`, and preserve generic MCP resource reads. - Add core event-mapping coverage that classifies `<skills>` developer messages as contextual history.
66 lines
2.1 KiB
Rust
66 lines
2.1 KiB
Rust
use codex_core::context::AvailableSkillsInstructions;
|
|
use codex_utils_string::take_bytes_at_char_boundary;
|
|
|
|
use crate::catalog::SkillCatalog;
|
|
|
|
const MAX_AVAILABLE_SKILLS_BYTES: usize = 8_000;
|
|
const MAX_MAIN_PROMPT_BYTES: usize = 8_000;
|
|
pub(crate) const MAX_SKILL_NAME_BYTES: usize = 256;
|
|
pub(crate) const MAX_SKILL_PATH_BYTES: usize = 1_024;
|
|
|
|
pub(crate) fn available_skills_fragment(
|
|
catalog: &SkillCatalog,
|
|
) -> Option<AvailableSkillsInstructions> {
|
|
let mut total_bytes = 0usize;
|
|
let mut omitted = 0usize;
|
|
let mut skill_lines = Vec::new();
|
|
|
|
for entry in catalog
|
|
.entries
|
|
.iter()
|
|
.filter(|entry| entry.enabled && entry.prompt_visible)
|
|
{
|
|
let description = entry
|
|
.short_description
|
|
.as_deref()
|
|
.unwrap_or(entry.description.as_str());
|
|
let line = render_skill_line(entry.name.as_str(), description, entry.rendered_path());
|
|
let next_bytes = total_bytes.saturating_add(line.len());
|
|
if next_bytes > MAX_AVAILABLE_SKILLS_BYTES {
|
|
omitted = omitted.saturating_add(1);
|
|
continue;
|
|
}
|
|
total_bytes = next_bytes;
|
|
skill_lines.push(line);
|
|
}
|
|
|
|
if skill_lines.is_empty() {
|
|
return None;
|
|
}
|
|
if omitted > 0 {
|
|
let skill_word = if omitted == 1 { "skill" } else { "skills" };
|
|
skill_lines.push(format!(
|
|
"- {omitted} additional {skill_word} omitted from this bounded skills list."
|
|
));
|
|
}
|
|
|
|
Some(AvailableSkillsInstructions::from_skill_lines(skill_lines))
|
|
}
|
|
|
|
fn render_skill_line(name: &str, description: &str, path: &str) -> String {
|
|
if description.is_empty() {
|
|
format!("- {name}: (file: {path})")
|
|
} else {
|
|
format!("- {name}: {description} (file: {path})")
|
|
}
|
|
}
|
|
|
|
pub(crate) fn truncate_main_prompt_contents(contents: &str) -> (String, bool) {
|
|
truncate_utf8_to_bytes(contents, MAX_MAIN_PROMPT_BYTES)
|
|
}
|
|
|
|
pub(crate) fn truncate_utf8_to_bytes(contents: &str, max_bytes: usize) -> (String, bool) {
|
|
let truncated = take_bytes_at_char_boundary(contents, max_bytes);
|
|
(truncated.to_string(), truncated.len() < contents.len())
|
|
}
|