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:
rphilizaire-openai authored and copyberry committed 2026-08-11 21:09:29 +00:00
1 parent ba2fb48319
commit a817d9424d
9 files changed
+351 -34

No files matched your search

+87
View File
@@ -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());
}
+2
View File
@@ -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;