mirror of
https://github.com/openai/codex.git
synced 2026-11-13 06:45:07 +00:00
Track implicit executor skill invocations (#38074)
## What changed - Detect executor-owned skill document reads and script executions across native and URI-based working directories. - Match skills only within the active execution environment while preserving host skill detection for local commands. - Emit resource-backed implicit invocation events and deduplicate repeated accesses within a turn. ## Testing - Add unit coverage for document reads, script executions, and environment-scoped matching. - Add an integration test for environment selection, resource-backed analytics, and repeated-access deduplication. GitOrigin-RevId: f78a48c3a4766fe8775bd88926b45a76761f6af8
This commit is contained in:
1 parent
ba2fb48319
commit
a817d9424d
9 files changed
+351
-34
No files matched your search
@@ -0,0 +1,87 @@
|
||||
use codex_analytics::InvocationType;
|
||||
use codex_analytics::SkillInvocation;
|
||||
use codex_analytics::SkillInvocationLocation;
|
||||
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
|
||||
use codex_extension_api::ExtensionData;
|
||||
use codex_skills::ImplicitSkillAccess;
|
||||
use codex_skills::detect_implicit_skill_invocation_for_command;
|
||||
use codex_skills::implicit_skill_accesses_for_command;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
|
||||
use crate::HostSkillsSnapshot;
|
||||
use crate::catalog::SkillSourceKind;
|
||||
use crate::state::ExecutorSkillsStepState;
|
||||
|
||||
/// Identifies the executor-owned or host-owned skill referenced by a command.
|
||||
pub fn detect_implicit_skill_invocation(
|
||||
turn_store: &ExtensionData,
|
||||
environment_id: &str,
|
||||
command: &str,
|
||||
workdir: &PathUri,
|
||||
native_workdir: Option<&AbsolutePathBuf>,
|
||||
) -> Option<SkillInvocation> {
|
||||
if environment_id == LOCAL_ENVIRONMENT_ID
|
||||
&& let Some(native_workdir) = native_workdir
|
||||
&& let Some(host_snapshot) = turn_store.get::<HostSkillsSnapshot>()
|
||||
&& let Some(skill) = detect_implicit_skill_invocation_for_command(
|
||||
host_snapshot.outcome(),
|
||||
command,
|
||||
native_workdir,
|
||||
)
|
||||
{
|
||||
return Some(SkillInvocation {
|
||||
skill_name: skill.name,
|
||||
location: SkillInvocationLocation::Host {
|
||||
path: skill.path_to_skills_md.to_path_buf(),
|
||||
scope: skill.scope,
|
||||
},
|
||||
plugin_id: skill.plugin_id,
|
||||
remote_plugin_id: skill.remote_plugin_id,
|
||||
invocation_type: InvocationType::Implicit,
|
||||
});
|
||||
}
|
||||
|
||||
let catalog = turn_store.get::<ExecutorSkillsStepState>()?;
|
||||
for access in implicit_skill_accesses_for_command(command, workdir) {
|
||||
for entry in &catalog.0.entries {
|
||||
let Some((skill_environment_id, skill_path)) = entry.main_prompt.environment_path()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if !entry.enabled
|
||||
|| entry.authority.kind != SkillSourceKind::Executor
|
||||
|| skill_environment_id != environment_id
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let matches = match &access {
|
||||
ImplicitSkillAccess::Document(path) => path == skill_path,
|
||||
ImplicitSkillAccess::Script(path) => skill_path
|
||||
.parent()
|
||||
.and_then(|skill_dir| skill_dir.join("scripts").ok())
|
||||
.is_some_and(|scripts_dir| path.starts_with(&scripts_dir)),
|
||||
};
|
||||
if matches {
|
||||
return Some(SkillInvocation {
|
||||
skill_name: entry.name.clone(),
|
||||
location: SkillInvocationLocation::Resource {
|
||||
id: entry.main_prompt.as_str().to_owned(),
|
||||
skill_id: entry.canonical_skill_id.clone(),
|
||||
scope: entry.analytics_scope,
|
||||
},
|
||||
plugin_id: None,
|
||||
remote_plugin_id: None,
|
||||
invocation_type: InvocationType::Implicit,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "invocation_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,69 @@
|
||||
use super::*;
|
||||
use crate::catalog::SkillAuthority;
|
||||
use crate::catalog::SkillCatalog;
|
||||
use crate::catalog::SkillCatalogEntry;
|
||||
use crate::catalog::SkillPackageId;
|
||||
use crate::catalog::SkillResourceId;
|
||||
|
||||
fn detect_executor_skill(command: &str, environments: &[&str]) -> Option<SkillInvocation> {
|
||||
let skill_path = PathUri::parse("file:///skills/demo/SKILL.md").expect("valid skill URI");
|
||||
let catalog = SkillCatalog {
|
||||
entries: environments
|
||||
.iter()
|
||||
.map(|environment| {
|
||||
SkillCatalogEntry::new(
|
||||
SkillPackageId(format!("skill://{environment}/demo")),
|
||||
SkillAuthority::new(SkillSourceKind::Executor, *environment),
|
||||
format!("{environment}-skill"),
|
||||
"test skill",
|
||||
SkillResourceId::environment(
|
||||
format!("skill://{environment}/demo/SKILL.md"),
|
||||
*environment,
|
||||
skill_path.clone(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
warnings: Vec::new(),
|
||||
};
|
||||
let turn_store = ExtensionData::new("test-turn");
|
||||
turn_store.insert(ExecutorSkillsStepState(catalog));
|
||||
|
||||
detect_implicit_skill_invocation(
|
||||
&turn_store,
|
||||
"executor-a",
|
||||
command,
|
||||
&PathUri::parse("file:///skills").expect("valid workdir URI"),
|
||||
/*native_workdir*/ None,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_executor_skill_document_reads() {
|
||||
let invocation = detect_executor_skill("cat demo/SKILL.md", &["executor-a"])
|
||||
.expect("document read should identify the executor skill");
|
||||
|
||||
assert_eq!(invocation.skill_name, "executor-a-skill");
|
||||
assert!(matches!(
|
||||
invocation.location,
|
||||
SkillInvocationLocation::Resource { id, .. }
|
||||
if id == "skill://executor-a/demo/SKILL.md"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_executor_skill_script_runs() {
|
||||
let invocation = detect_executor_skill("python demo/scripts/run.py", &["executor-a"])
|
||||
.expect("script execution should identify the executor skill");
|
||||
|
||||
assert_eq!(invocation.skill_name, "executor-a-skill");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_matching_paths_from_other_environments() {
|
||||
let invocation = detect_executor_skill("cat demo/SKILL.md", &["executor-b", "executor-a"])
|
||||
.expect("matching path should resolve to the execution environment");
|
||||
|
||||
assert_eq!(invocation.skill_name, "executor-a-skill");
|
||||
assert!(detect_executor_skill("cat demo/SKILL.md", &["executor-b"]).is_none());
|
||||
}
|
||||
@@ -11,6 +11,7 @@ mod host_prompt;
|
||||
mod host_roots;
|
||||
mod host_service;
|
||||
mod host_snapshot;
|
||||
mod invocation;
|
||||
mod loader;
|
||||
pub mod provider;
|
||||
mod render;
|
||||
@@ -35,6 +36,7 @@ pub use host_service::HostSkillsLoadInput;
|
||||
pub use host_service::HostSkillsRequest;
|
||||
pub use host_service::HostSkillsService;
|
||||
pub use host_snapshot::HostSkillsSnapshot;
|
||||
pub use invocation::detect_implicit_skill_invocation;
|
||||
pub use provider::ExecutorSkillProvider;
|
||||
pub use provider::HostSkillProvider;
|
||||
pub use provider::OrchestratorSkillProvider;
|
||||
|
||||
Reference in new issue
Block a user