mirror of
https://github.com/openai/codex.git
synced 2026-09-17 12:23:33 +00:00
Merge branch 'main' into tag
This commit is contained in:
@@ -1081,6 +1081,7 @@ pub enum SkillScope {
|
||||
User,
|
||||
Repo,
|
||||
System,
|
||||
Admin,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
@@ -1131,6 +1132,7 @@ impl From<CoreSkillScope> for SkillScope {
|
||||
CoreSkillScope::User => Self::User,
|
||||
CoreSkillScope::Repo => Self::Repo,
|
||||
CoreSkillScope::System => Self::System,
|
||||
CoreSkillScope::Admin => Self::Admin,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
id: Feature::Skills,
|
||||
key: "skills",
|
||||
stage: Stage::Experimental,
|
||||
default_enabled: false,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Tui2,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: plan
|
||||
description: Generate a plan for how an agent should accomplish a complex coding task. Use when a user asks for a plan, and optionally when they want to save, find, read, update, or delete plan files in $CODEX_HOME/plans (default ~/.codex/plans).
|
||||
metadata:
|
||||
short-description: Create and manage plan markdown files under $CODEX_HOME/plans.
|
||||
short-description: Generate a plan for a complex task
|
||||
---
|
||||
|
||||
# Plan
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
name: skill-creator
|
||||
description: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Codex's capabilities with specialized knowledge, workflows, or tool integrations.
|
||||
metadata:
|
||||
short-description: Create or update a skill
|
||||
---
|
||||
|
||||
# Skill Creator
|
||||
|
||||
@@ -33,6 +33,7 @@ struct SkillFrontmatterMetadata {
|
||||
const SKILLS_FILENAME: &str = "SKILL.md";
|
||||
const SKILLS_DIR_NAME: &str = "skills";
|
||||
const REPO_ROOT_CONFIG_DIR_NAME: &str = ".codex";
|
||||
const ADMIN_SKILLS_ROOT: &str = "/etc/codex/skills";
|
||||
const MAX_NAME_LEN: usize = 64;
|
||||
const MAX_DESCRIPTION_LEN: usize = 1024;
|
||||
const MAX_SHORT_DESCRIPTION_LEN: usize = MAX_DESCRIPTION_LEN;
|
||||
@@ -108,6 +109,13 @@ pub(crate) fn system_skills_root(codex_home: &Path) -> SkillRoot {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn admin_skills_root() -> SkillRoot {
|
||||
SkillRoot {
|
||||
path: PathBuf::from(ADMIN_SKILLS_ROOT),
|
||||
scope: SkillScope::Admin,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn repo_skills_root(cwd: &Path) -> Option<SkillRoot> {
|
||||
let base = if cwd.is_dir() { cwd } else { cwd.parent()? };
|
||||
let base = normalize_path(base).unwrap_or_else(|_| base.to_path_buf());
|
||||
@@ -140,21 +148,28 @@ pub(crate) fn repo_skills_root(cwd: &Path) -> Option<SkillRoot> {
|
||||
})
|
||||
}
|
||||
|
||||
fn skill_roots(config: &Config) -> Vec<SkillRoot> {
|
||||
pub(crate) fn skill_roots_for_cwd(codex_home: &Path, cwd: &Path) -> Vec<SkillRoot> {
|
||||
let mut roots = Vec::new();
|
||||
|
||||
if let Some(repo_root) = repo_skills_root(&config.cwd) {
|
||||
if let Some(repo_root) = repo_skills_root(cwd) {
|
||||
roots.push(repo_root);
|
||||
}
|
||||
|
||||
// Load order matters: we dedupe by name, keeping the first occurrence.
|
||||
// This makes repo/user skills win over system skills.
|
||||
roots.push(user_skills_root(&config.codex_home));
|
||||
roots.push(system_skills_root(&config.codex_home));
|
||||
// Priority order: repo, user, system, then admin.
|
||||
roots.push(user_skills_root(codex_home));
|
||||
roots.push(system_skills_root(codex_home));
|
||||
if cfg!(unix) {
|
||||
roots.push(admin_skills_root());
|
||||
}
|
||||
|
||||
roots
|
||||
}
|
||||
|
||||
fn skill_roots(config: &Config) -> Vec<SkillRoot> {
|
||||
skill_roots_for_cwd(&config.codex_home, &config.cwd)
|
||||
}
|
||||
|
||||
fn discover_skills_under_root(root: &Path, scope: SkillScope, outcome: &mut SkillLoadOutcome) {
|
||||
let Ok(root) = normalize_path(root) else {
|
||||
return;
|
||||
@@ -622,7 +637,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn loads_system_skills_with_lowest_priority() {
|
||||
async fn loads_system_skills_when_present() {
|
||||
let codex_home = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
write_system_skill(&codex_home, "system", "dupe-skill", "from system");
|
||||
@@ -764,6 +779,51 @@ mod tests {
|
||||
assert_eq!(outcome.skills[0].scope, SkillScope::System);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn skill_roots_include_admin_with_lowest_priority_on_unix() {
|
||||
let codex_home = tempfile::tempdir().expect("tempdir");
|
||||
let cfg = make_config(&codex_home).await;
|
||||
|
||||
let scopes: Vec<SkillScope> = skill_roots(&cfg)
|
||||
.into_iter()
|
||||
.map(|root| root.scope)
|
||||
.collect();
|
||||
let mut expected = vec![SkillScope::User, SkillScope::System];
|
||||
if cfg!(unix) {
|
||||
expected.push(SkillScope::Admin);
|
||||
}
|
||||
assert_eq!(scopes, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn deduplicates_by_name_preferring_system_over_admin() {
|
||||
let system_dir = tempfile::tempdir().expect("tempdir");
|
||||
let admin_dir = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
write_skill_at(system_dir.path(), "system", "dupe-skill", "from system");
|
||||
write_skill_at(admin_dir.path(), "admin", "dupe-skill", "from admin");
|
||||
|
||||
let outcome = load_skills_from_roots([
|
||||
SkillRoot {
|
||||
path: system_dir.path().to_path_buf(),
|
||||
scope: SkillScope::System,
|
||||
},
|
||||
SkillRoot {
|
||||
path: admin_dir.path().to_path_buf(),
|
||||
scope: SkillScope::Admin,
|
||||
},
|
||||
]);
|
||||
|
||||
assert!(
|
||||
outcome.errors.is_empty(),
|
||||
"unexpected errors: {:?}",
|
||||
outcome.errors
|
||||
);
|
||||
assert_eq!(outcome.skills.len(), 1);
|
||||
assert_eq!(outcome.skills[0].name, "dupe-skill");
|
||||
assert_eq!(outcome.skills[0].scope, SkillScope::System);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn deduplicates_by_name_preferring_user_over_system() {
|
||||
let codex_home = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
@@ -5,9 +5,7 @@ use std::sync::RwLock;
|
||||
|
||||
use crate::skills::SkillLoadOutcome;
|
||||
use crate::skills::loader::load_skills_from_roots;
|
||||
use crate::skills::loader::repo_skills_root;
|
||||
use crate::skills::loader::system_skills_root;
|
||||
use crate::skills::loader::user_skills_root;
|
||||
use crate::skills::loader::skill_roots_for_cwd;
|
||||
use crate::skills::system::install_system_skills;
|
||||
pub struct SkillsManager {
|
||||
codex_home: PathBuf,
|
||||
@@ -39,12 +37,7 @@ impl SkillsManager {
|
||||
return outcome;
|
||||
}
|
||||
|
||||
let mut roots = Vec::new();
|
||||
if let Some(repo_root) = repo_skills_root(cwd) {
|
||||
roots.push(repo_root);
|
||||
}
|
||||
roots.push(user_skills_root(&self.codex_home));
|
||||
roots.push(system_skills_root(&self.codex_home));
|
||||
let roots = skill_roots_for_cwd(&self.codex_home, cwd);
|
||||
let outcome = load_skills_from_roots(roots);
|
||||
match self.cache_by_cwd.write() {
|
||||
Ok(mut cache) => {
|
||||
|
||||
@@ -1721,6 +1721,7 @@ pub enum SkillScope {
|
||||
User,
|
||||
Repo,
|
||||
System,
|
||||
Admin,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
|
||||
@@ -1284,9 +1284,9 @@ async fn unified_exec_end_after_task_complete_is_suppressed() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unified_exec_waiting_multiple_empty_snapshots() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None);
|
||||
#[tokio::test]
|
||||
async fn unified_exec_waiting_multiple_empty_snapshots() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
begin_unified_exec_startup(&mut chat, "call-wait-1", "proc-1", "just fix");
|
||||
|
||||
terminal_interaction(&mut chat, "call-wait-1a", "proc-1", "");
|
||||
@@ -1311,9 +1311,9 @@ fn unified_exec_waiting_multiple_empty_snapshots() {
|
||||
assert_snapshot!("unified_exec_waiting_multiple_empty_after", combined);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unified_exec_empty_then_non_empty_snapshot() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None);
|
||||
#[tokio::test]
|
||||
async fn unified_exec_empty_then_non_empty_snapshot() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
begin_unified_exec_startup(&mut chat, "call-wait-2", "proc-2", "just fix");
|
||||
|
||||
terminal_interaction(&mut chat, "call-wait-2a", "proc-2", "");
|
||||
@@ -1327,9 +1327,9 @@ fn unified_exec_empty_then_non_empty_snapshot() {
|
||||
assert_snapshot!("unified_exec_empty_then_non_empty_after", combined);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unified_exec_non_empty_then_empty_snapshots() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None);
|
||||
#[tokio::test]
|
||||
async fn unified_exec_non_empty_then_empty_snapshots() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
begin_unified_exec_startup(&mut chat, "call-wait-3", "proc-3", "just fix");
|
||||
|
||||
terminal_interaction(&mut chat, "call-wait-3a", "proc-3", "pwd\n");
|
||||
|
||||
Reference in New Issue
Block a user