use std::collections::HashSet;
use std::fmt::Write as FmtWrite;
use std::fs;
use std::io::{self, Read, Write};
#[cfg(any(target_vendor = "apple", target_os = "linux"))]
use std::os::fd::AsFd;
use std::path::{Component, Path, PathBuf};
use std::sync::Arc;
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use cap_std::ambient_authority;
#[cfg(unix)]
use cap_std::fs::MetadataExt as CapMetadataExt;
use cap_std::fs::{Dir, OpenOptions};
use chrono::{SecondsFormat, Utc};
use reqwest::Client;
#[cfg(any(target_vendor = "apple", target_os = "linux"))]
use rustix::fs::{RenameFlags, renameat_with};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use uuid::Uuid;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt as StdMetadataExt;
use crate::domain::errors::{AgentError, AgentResult, ErrorCode};
use crate::domain::pi_rpc::PiCommandInfo;
use crate::domain::policy::CommandPolicy;
use crate::domain::protocol::{InstalledSkill, SkillOrigin, SkillScope};
use crate::domain::skills::{CatalogSkill, MAX_RESPONSE_BYTES, SkillFile};
#[cfg(test)]
use super::skill_source::github_fetcher_with_base;
use super::{
pi_skill_discovery::{PiSkillSource, discover_pi_skills, parse_lenient_skill_metadata},
skill_source::{SkillSourceFetcher, extract_archive, github_fetcher},
};
const MANIFEST_NAME: &str = ".regy-skill.json";
const MANIFEST_SCHEMA_VERSION: u8 = 1;
const LOCATOR_VERSION: u8 = 1;
pub(super) const MAX_FILES: usize = 256;
pub(super) const MAX_FILE_BYTES: usize = 2 * 1024 * 1024;
pub(super) const MAX_TOTAL_BYTES: usize = 16 * 1024 * 1024;
const MAX_MANIFEST_BYTES: u64 = 64 * 1024;
const MAX_LOCATOR_BYTES: usize = 4096;
const MAX_REQUESTED_WORKSPACES: usize = 64;
const MAX_LISTED_SKILLS: usize = 512;
#[derive(Clone)]
pub struct SkillStore {
global_root: PathBuf,
home_root: PathBuf,
policy: CommandPolicy,
source_fetcher: Arc<dyn SkillSourceFetcher>,
directory_operations: Arc<dyn DirectoryOperations>,
root_opened_hook: Arc<dyn RootOpenedHook>,
}
pub(crate) struct PendingSkillInstall {
store: SkillStore,
root: OpenedSkillRoot,
destination: String,
replacement: Option<Replacement>,
item: Option<InstalledSkill>,
skill_markdown_path: PathBuf,
}
impl PendingSkillInstall {
pub(crate) fn item(&self) -> &InstalledSkill {
self.item
.as_ref()
.expect("pending skill install always has an item")
}
pub(crate) fn skill_markdown_path(&self) -> &Path {
&self.skill_markdown_path
}
pub(crate) fn commit(mut self) -> AgentResult<InstalledSkill> {
if let Some(replacement) = self.replacement.take()
&& let Some(backup) = replacement.backup
{
let _ = self
.store
.directory_operations
.remove_open_dir_all(backup.dir);
}
self.item.take().ok_or_else(filesystem_failed)
}
pub(crate) fn rollback(mut self) -> AgentResult<()> {
let Some(replacement) = self.replacement.take() else {
return Ok(());
};
self.item.take();
self.store.rollback_replacement(
&self.root.dir,
self.root.scope.clone(),
&self.destination,
&replacement,
)
}
}
impl Drop for PendingSkillInstall {
fn drop(&mut self) {
let Some(replacement) = self.replacement.take() else {
return;
};
let _ = self.store.rollback_replacement(
&self.root.dir,
self.root.scope.clone(),
&self.destination,
&replacement,
);
}
}
impl std::fmt::Debug for SkillStore {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("SkillStore")
.field("global_root", &self.global_root)
.finish_non_exhaustive()
}
}
impl SkillStore {
pub fn new(
global_root: PathBuf,
home_root: PathBuf,
policy: CommandPolicy,
client: Client,
) -> Self {
Self {
global_root,
home_root,
policy,
source_fetcher: github_fetcher(client),
directory_operations: Arc::new(StdDirectoryOperations),
root_opened_hook: Arc::new(NoopRootOpenedHook),
}
}
#[cfg(test)]
pub(crate) fn configured_global_root(&self) -> &Path {
&self.global_root
}
#[cfg(test)]
pub(crate) fn with_test_dependencies(
global_root: PathBuf,
policy: CommandPolicy,
source_fetcher: Arc<dyn SkillSourceFetcher>,
directory_operations: Arc<dyn DirectoryOperations>,
) -> Self {
let home_root = test_home_root(&global_root);
Self {
global_root,
home_root,
policy,
source_fetcher,
directory_operations,
root_opened_hook: Arc::new(NoopRootOpenedHook),
}
}
#[cfg(test)]
pub(crate) fn with_test_dependencies_and_hook(
global_root: PathBuf,
policy: CommandPolicy,
source_fetcher: Arc<dyn SkillSourceFetcher>,
directory_operations: Arc<dyn DirectoryOperations>,
root_opened_hook: Arc<dyn RootOpenedHook>,
) -> Self {
let home_root = test_home_root(&global_root);
Self {
global_root,
home_root,
policy,
source_fetcher,
directory_operations,
root_opened_hook,
}
}
#[cfg(test)]
pub(crate) fn with_github_api_base_for_test(
global_root: PathBuf,
policy: CommandPolicy,
client: Client,
api_base: &str,
) -> AgentResult<Self> {
let home_root = test_home_root(&global_root);
Ok(Self {
global_root,
home_root,
policy,
source_fetcher: github_fetcher_with_base(client, api_base)?,
directory_operations: Arc::new(StdDirectoryOperations),
root_opened_hook: Arc::new(NoopRootOpenedHook),
})
}
#[cfg(test)]
pub(crate) fn with_root_hook_for_test(
global_root: PathBuf,
policy: CommandPolicy,
client: Client,
root_opened_hook: Arc<dyn RootOpenedHook>,
) -> Self {
let home_root = test_home_root(&global_root);
Self {
global_root,
home_root,
policy,
source_fetcher: github_fetcher(client),
directory_operations: Arc::new(StdDirectoryOperations),
root_opened_hook,
}
}
#[cfg(test)]
pub fn list(&self, workspace: Option<&Path>) -> AgentResult<Vec<InstalledSkill>> {
self.list_all_for_display(workspace, &[])
}
#[cfg(test)]
pub fn list_all_for_display(
&self,
workspace: Option<&Path>,
additional_workspaces: &[String],
) -> AgentResult<Vec<InstalledSkill>> {
let discovered = self.filesystem_discoveries(workspace, additional_workspaces)?;
Ok(finalize_discoveries(discovered))
}
pub(crate) fn list_with_runtime_commands(
&self,
workspace: Option<&Path>,
additional_workspaces: &[String],
runtime_commands: Vec<WorkspacePiCommands>,
) -> AgentResult<Vec<InstalledSkill>> {
let mut discovered = self.filesystem_discoveries(workspace, additional_workspaces)?;
for workspace_commands in runtime_commands {
for command in workspace_commands.commands {
if let Some(runtime) = runtime_discovery(&workspace_commands.workspace, command) {
discovered.push(runtime);
}
}
}
Ok(finalize_discoveries(discovered))
}
pub(crate) fn validated_runtime_workspaces(
&self,
workspace: Option<&Path>,
additional_workspaces: &[String],
) -> AgentResult<Vec<PathBuf>> {
self.validated_requested_workspaces(workspace, additional_workspaces)
}
fn filesystem_discoveries(
&self,
workspace: Option<&Path>,
additional_workspaces: &[String],
) -> AgentResult<Vec<ListedDiscovery>> {
let roots = self.discovery_roots(workspace, additional_workspaces)?;
let mut discovered = Vec::new();
for root in roots {
for source in discover_pi_skills(&root.path, root.accept_root_markdown)? {
if let Some(item) = self.installed_from_pi_source(&root, &source)? {
discovered.push(ListedDiscovery {
canonical_source: Some(source.canonical_path),
item,
});
}
}
}
Ok(discovered)
}
fn discovery_roots(
&self,
workspace: Option<&Path>,
additional_workspaces: &[String],
) -> AgentResult<Vec<DiscoveryRoot>> {
if additional_workspaces.len() > MAX_REQUESTED_WORKSPACES {
return Err(skill_invalid());
}
let mut roots = vec![
DiscoveryRoot {
path: self.global_root.clone(),
accept_root_markdown: true,
scope: SkillScope::Global,
workspace: None,
},
DiscoveryRoot {
path: self.home_root.join(".agents/skills"),
accept_root_markdown: false,
scope: SkillScope::Global,
workspace: None,
},
];
let workspaces = self.validated_requested_workspaces(workspace, additional_workspaces)?;
for workspace in workspaces {
let workspace_string = workspace.to_string_lossy().into_owned();
roots.push(DiscoveryRoot {
path: workspace.join(".pi/skills"),
accept_root_markdown: true,
scope: SkillScope::Workspace,
workspace: Some(workspace_string),
});
self.append_ancestor_agent_roots(&workspace, &mut roots);
}
let mut seen = HashSet::new();
roots.retain(|root| {
seen.insert((
root.path.clone(),
matches!(root.scope, SkillScope::Workspace),
root.workspace.clone(),
))
});
Ok(roots)
}
fn validated_requested_workspaces(
&self,
workspace: Option<&Path>,
additional_workspaces: &[String],
) -> AgentResult<Vec<PathBuf>> {
if additional_workspaces.len() > MAX_REQUESTED_WORKSPACES {
return Err(skill_invalid());
}
let mut workspaces = Vec::new();
let mut canonical_workspaces = HashSet::new();
for requested in workspace
.into_iter()
.map(Path::to_path_buf)
.chain(additional_workspaces.iter().map(PathBuf::from))
{
let canonical = self.policy.validate_workspace(&requested)?;
if canonical_workspaces.insert(canonical.clone()) {
workspaces.push(PathBuf::from(canonical));
}
}
Ok(workspaces)
}
fn append_ancestor_agent_roots(&self, workspace: &Path, roots: &mut Vec<DiscoveryRoot>) {
let git_root = workspace.ancestors().find(|ancestor| {
fs::symlink_metadata(ancestor.join(".git"))
.is_ok_and(|metadata| !metadata.file_type().is_symlink())
});
for ancestor in workspace.ancestors() {
if let Ok(workspace) = self.policy.validate_workspace(ancestor) {
roots.push(DiscoveryRoot {
path: ancestor.join(".agents/skills"),
accept_root_markdown: false,
scope: SkillScope::Workspace,
workspace: Some(workspace),
});
}
if git_root.is_some_and(|root| root == ancestor) {
break;
}
}
}
fn installed_from_pi_source(
&self,
root: &DiscoveryRoot,
source: &PiSkillSource,
) -> AgentResult<Option<InstalledSkill>> {
let manifest = match source.canonical_path.parent() {
Some(directory) => read_manifest_path(directory)?,
None => None,
}
.filter(|manifest| {
manifest_matches_location(manifest, &root.scope, root.workspace.as_deref())
});
if let Some(manifest) = manifest {
let bytes = read_bounded_path(&source.canonical_path, MAX_FILE_BYTES)?;
let metadata = match parse_skill_metadata(&bytes) {
Ok(metadata) => metadata,
Err(_) => return Ok(None),
};
let directory_name = match source
.canonical_path
.parent()
.and_then(Path::file_name)
.and_then(|name| name.to_str())
{
Some(name) if valid_skill_name(name) => name.to_owned(),
_ => return Ok(None),
};
return Ok(Some(InstalledSkill {
installation_id: encode_locator(&InstallationLocator {
version: LOCATOR_VERSION,
scope: root.scope.clone(),
workspace: root.workspace.clone(),
name: directory_name,
})?,
name: metadata.name,
description: metadata.description,
provider: Some(manifest.provider),
skill_id: Some(manifest.skill_id),
source: Some(manifest.source),
scope: root.scope.clone(),
workspace: root.workspace.clone(),
managed: true,
content_hash: Some(manifest.content_hash),
installed_at: Some(manifest.installed_at),
origin: SkillOrigin::Regy,
}));
}
Ok(Some(InstalledSkill {
installation_id: pi_installation_id(&source.canonical_path),
name: source.name.clone(),
description: source.description.clone(),
provider: None,
skill_id: None,
source: None,
scope: root.scope.clone(),
workspace: root.workspace.clone(),
managed: false,
content_hash: None,
installed_at: None,
origin: SkillOrigin::Pi,
}))
}
#[cfg(test)]
pub async fn install(
&self,
provider: &str,
skill: CatalogSkill,
scope: SkillScope,
workspace: Option<&Path>,
confirm_update: bool,
) -> AgentResult<InstalledSkill> {
let pending = self
.begin_install(provider, skill, scope, workspace, confirm_update)
.await?;
tokio::task::spawn_blocking(move || pending.commit())
.await
.map_err(|_| filesystem_failed())?
}
pub(crate) async fn begin_install(
&self,
provider: &str,
skill: CatalogSkill,
scope: SkillScope,
workspace: Option<&Path>,
confirm_update: bool,
) -> AgentResult<PendingSkillInstall> {
let provider = provider.trim();
if provider.is_empty() || skill.item.id.trim().is_empty() {
return Err(skill_invalid());
}
let location = self.install_location(scope.clone(), workspace)?;
let root = {
let store = self.clone();
tokio::task::spawn_blocking(move || store.prepare_root(&location))
.await
.map_err(|_| filesystem_failed())??
};
let source = skill.source.clone();
let payload = if let Some(files) = source.files.clone() {
SourcePayload::Files {
files,
revision: source.revision.clone(),
}
} else {
if provider != "skillspool" {
return Err(skill_invalid());
}
let fetched = self.source_fetcher.fetch(&source).await?;
SourcePayload::Archive {
bytes: fetched.bytes,
directory: source.directory.clone(),
revision: fetched.revision,
}
};
let store = self.clone();
let provider = provider.to_owned();
tokio::task::spawn_blocking(move || {
store.begin_install_blocking(provider, skill, scope, root, payload, confirm_update)
})
.await
.map_err(|_| filesystem_failed())?
}
pub fn uninstall(&self, installation_id: &str) -> AgentResult<()> {
if installation_id.starts_with("pi:") {
return Err(skill_conflict());
}
let locator = decode_locator(installation_id)?;
validate_locator(&locator)?;
let root_path = match locator.scope {
SkillScope::Global => self.global_root.clone(),
SkillScope::Workspace => {
let workspace = locator.workspace.as_deref().ok_or_else(skill_not_found)?;
let workspace = self.policy.validate_workspace(Path::new(workspace))?;
Path::new(&workspace).join(".agents/skills")
}
};
let root = self
.open_existing_root(&root_path, locator.scope.clone(), locator.workspace.clone())?
.ok_or_else(skill_not_found)?;
let discovered = self
.listed_from_open_root(&root)?
.into_iter()
.find(|item| item.installation_id == installation_id)
.ok_or_else(skill_not_found)?;
let quarantined = self
.quarantine_destination(&root, &locator.name)?
.ok_or_else(skill_not_found)?;
match read_manifest(&quarantined.dir) {
Ok(Some(manifest))
if manifest_matches_location(
&manifest,
&locator.scope,
locator.workspace.as_deref(),
) && manifest_matches_installed_record(&manifest, &discovered) => {}
Ok(_) => {
self.restore_quarantine_if_free(&root.dir, &quarantined.name, &locator.name)?;
return Err(AgentError::new(
ErrorCode::SkillConflict,
"unmanaged skill cannot be removed",
));
}
Err(error) => {
self.restore_quarantine_if_free(&root.dir, &quarantined.name, &locator.name)?;
return Err(error);
}
}
self.directory_operations
.remove_open_dir_all(quarantined.dir)
.map_err(|_| filesystem_failed())
}
fn install_location(
&self,
scope: SkillScope,
workspace: Option<&Path>,
) -> AgentResult<InstallLocation> {
match scope {
SkillScope::Global => {
if workspace.is_some() {
return Err(scope_denied());
}
Ok(InstallLocation {
root: self.global_root.clone(),
workspace: None,
})
}
SkillScope::Workspace => {
let workspace = workspace.ok_or_else(scope_denied)?;
let workspace = self.policy.validate_workspace(workspace)?;
Ok(InstallLocation {
root: Path::new(&workspace).join(".agents/skills"),
workspace: Some(workspace),
})
}
}
}
fn begin_install_blocking(
&self,
provider: String,
skill: CatalogSkill,
scope: SkillScope,
root: OpenedSkillRoot,
payload: SourcePayload,
confirm_update: bool,
) -> AgentResult<PendingSkillInstall> {
let (files, revision) = match payload {
SourcePayload::Files { files, revision } => (files, revision),
SourcePayload::Archive {
bytes,
directory,
revision,
} => (extract_archive(&bytes, &directory)?, revision),
};
let validated = validate_files(files)?;
let destination = validated.metadata.name.as_str();
let content_hash = content_hash(&validated.files);
let manifest = ManagedSkillManifest {
schema_version: MANIFEST_SCHEMA_VERSION,
provider: provider.clone(),
skill_id: skill.item.id.clone(),
source: skill.source.repository.clone(),
revision,
content_hash: content_hash.clone(),
scope: scope.clone(),
workspace: root.workspace.clone(),
installed_at: Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true),
};
let manifest_bytes = serialize_manifest(&manifest)?;
let staged = format!(".regy-stage-{}", Uuid::new_v4());
root.dir
.create_dir(&staged)
.map_err(|_| filesystem_failed())?;
let staged_dir = root
.dir
.open_dir(&staged)
.map_err(|_| filesystem_failed())?;
let staged_identity = capability_metadata_identity(
&staged_dir.dir_metadata().map_err(|_| filesystem_failed())?,
)?;
if let Err(error) = write_staged(&staged_dir, &validated.files, &manifest_bytes) {
let _ = self.directory_operations.remove_open_dir_all(staged_dir);
return Err(error);
}
let discovered = match self.discovered_by_directory_name(&root, destination) {
Ok(discovered) => discovered,
Err(error) => {
let _ = self.directory_operations.remove_open_dir_all(staged_dir);
return Err(error);
}
};
let quarantined = match self.quarantine_destination(&root, destination) {
Ok(quarantined) => quarantined,
Err(error) => {
let _ = self.directory_operations.remove_open_dir_all(staged_dir);
return Err(error);
}
};
let backup = match quarantined {
Some(quarantined) => {
let manifest = match read_manifest(&quarantined.dir) {
Ok(Some(manifest)) => manifest,
Ok(None) => {
let restored = self.restore_quarantine_if_free(
&root.dir,
&quarantined.name,
destination,
);
let _ = self.directory_operations.remove_open_dir_all(staged_dir);
restored?;
return Err(skill_conflict());
}
Err(error) => {
let restored = self.restore_quarantine_if_free(
&root.dir,
&quarantined.name,
destination,
);
let _ = self.directory_operations.remove_open_dir_all(staged_dir);
restored?;
return Err(error);
}
};
let matches_discovery = discovered
.as_ref()
.is_some_and(|item| manifest_matches_installed_record(&manifest, item));
if !matches_discovery
|| manifest.provider != provider
|| manifest.skill_id != skill.item.id
{
let restored =
self.restore_quarantine_if_free(&root.dir, &quarantined.name, destination);
let _ = self.directory_operations.remove_open_dir_all(staged_dir);
restored?;
return Err(skill_conflict());
}
if !confirm_update {
let restored =
self.restore_quarantine_if_free(&root.dir, &quarantined.name, destination);
let _ = self.directory_operations.remove_open_dir_all(staged_dir);
restored?;
return Err(AgentError::new(
ErrorCode::SkillAlreadyInstalled,
"skill update requires confirmation",
));
}
Some(quarantined)
}
None if discovered.is_some() => {
let _ = self.directory_operations.remove_open_dir_all(staged_dir);
return Err(skill_conflict());
}
None => None,
};
let replacement = match self.replace_directory(
&root.dir,
&staged,
destination,
backup,
staged_identity,
) {
Ok(replacement) => replacement,
Err(error) => {
let _ = self.directory_operations.remove_open_dir_all(staged_dir);
return Err(error);
}
};
let verified = root
.dir
.open_dir(destination)
.map_err(|_| filesystem_failed())
.and_then(|named_destination| {
verify_directory_identity(&named_destination, staged_identity)?;
self.installed_from_open_directory(&root, destination, &staged_dir)
.and_then(|item| item.ok_or_else(filesystem_failed))
})
.and_then(|item| {
if item.managed
&& item.provider.as_deref() == Some(provider.as_str())
&& item.skill_id.as_deref() == Some(skill.item.id.as_str())
&& item.scope == scope
&& item.name == validated.metadata.name
&& item.content_hash.as_deref() == Some(content_hash.as_str())
{
Ok(item)
} else {
Err(filesystem_failed())
}
});
let installed = match verified {
Ok(installed) => installed,
Err(error) => {
self.rollback_replacement(
&root.dir,
root.scope.clone(),
destination,
&replacement,
)?;
return Err(error);
}
};
let skill_markdown_candidate = match scope {
SkillScope::Global => self.global_root.join(destination).join("SKILL.md"),
SkillScope::Workspace => {
Path::new(root.workspace.as_deref().ok_or_else(filesystem_failed)?)
.join(".agents/skills")
.join(destination)
.join("SKILL.md")
}
};
let skill_markdown_path = skill_markdown_candidate
.parent()
.and_then(|directory| ambient_directory_identity(directory).ok())
.filter(|identity| *identity == staged_identity)
.and_then(|_| skill_markdown_candidate.canonicalize().ok())
.unwrap_or(skill_markdown_candidate);
Ok(PendingSkillInstall {
store: self.clone(),
root,
destination: destination.to_owned(),
replacement: Some(replacement),
item: Some(installed),
skill_markdown_path,
})
}
fn quarantine_destination(
&self,
root: &OpenedSkillRoot,
destination: &str,
) -> AgentResult<Option<QuarantinedChild>> {
let quarantine = format!(".regy-quarantine-{destination}-{}", Uuid::new_v4());
self.root_opened_hook
.before_destination_quarantine(root.scope.clone(), destination);
match self.directory_operations.rename_noreplace(
&root.dir,
Path::new(destination),
Path::new(&quarantine),
) {
Ok(()) => {}
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(_) => return Err(filesystem_failed()),
}
self.root_opened_hook
.after_destination_quarantined(root.scope.clone(), &quarantine);
let opened = root.dir.symlink_metadata(&quarantine).and_then(|metadata| {
if !metadata.file_type().is_dir() || metadata.file_type().is_symlink() {
return Err(io::Error::other("quarantined child is not a directory"));
}
let expected = capability_metadata_identity(&metadata)
.map_err(|_| io::Error::other("invalid quarantined identity"))?;
let directory = root.dir.open_dir(&quarantine)?;
verify_directory_identity(&directory, expected)
.map_err(|_| io::Error::other("quarantined identity changed"))?;
Ok(directory)
});
match opened {
Ok(dir) => Ok(Some(QuarantinedChild {
name: quarantine,
dir,
})),
Err(_) => {
self.restore_quarantine_if_free(&root.dir, &quarantine, destination)?;
Err(skill_conflict())
}
}
}
fn restore_quarantine_if_free(
&self,
root: &Dir,
quarantine: &str,
destination: &str,
) -> AgentResult<bool> {
match self.directory_operations.rename_noreplace(
root,
Path::new(quarantine),
Path::new(destination),
) {
Ok(()) => Ok(true),
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => Ok(false),
Err(_) => Err(filesystem_failed()),
}
}
fn replace_directory(
&self,
root: &Dir,
staged: &str,
destination: &str,
backup: Option<QuarantinedChild>,
activated_identity: DirectoryIdentity,
) -> AgentResult<Replacement> {
if self
.directory_operations
.rename_noreplace(root, Path::new(staged), Path::new(destination))
.is_err()
{
if let Some(backup) = backup.as_ref() {
let _ = self.restore_quarantine_if_free(root, &backup.name, destination);
}
return Err(filesystem_failed());
}
Ok(Replacement {
backup,
activated_identity,
})
}
fn rollback_replacement(
&self,
root: &Dir,
scope: SkillScope,
destination: &str,
replacement: &Replacement,
) -> AgentResult<()> {
let failed = format!(".regy-failed-{destination}-{}", Uuid::new_v4());
let cleanup_result = match self.directory_operations.rename_noreplace(
root,
Path::new(destination),
Path::new(&failed),
) {
Ok(()) => match root.open_dir(&failed) {
Ok(failed_dir) => {
self.root_opened_hook
.after_cleanup_handle_opened(scope, &failed);
let failed_identity = failed_dir
.dir_metadata()
.map_err(|_| filesystem_failed())
.and_then(|metadata| capability_metadata_identity(&metadata));
match failed_identity {
Ok(identity) if identity == replacement.activated_identity => self
.directory_operations
.remove_open_dir_all(failed_dir)
.map_err(|_| filesystem_failed()),
Ok(_) => {
let _ = self.restore_quarantine_if_free(root, &failed, destination);
Err(filesystem_failed())
}
Err(_) => Err(filesystem_failed()),
}
}
Err(_) => Err(filesystem_failed()),
},
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
Err(_) => Err(filesystem_failed()),
};
let restore_result = replacement.backup.as_ref().map_or(Ok(()), |backup| {
match self.restore_quarantine_if_free(root, &backup.name, destination) {
Ok(true) => Ok(()),
Ok(false) | Err(_) => Err(filesystem_failed()),
}
});
match (cleanup_result, restore_result) {
(Ok(()), Ok(())) => Ok(()),
(Err(error), Ok(())) | (Ok(()), Err(error)) => Err(error),
(Err(_), Err(_)) => Err(AgentError::new(
ErrorCode::SkillFilesystemFailed,
"post-install cleanup and backup restoration failed",
)),
}
}
fn scan_open_root(
&self,
root: &OpenedSkillRoot,
installed: &mut Vec<InstalledSkill>,
) -> AgentResult<()> {
let entries = root.dir.entries().map_err(|_| filesystem_failed())?;
for entry in entries {
let entry = entry.map_err(|_| filesystem_failed())?;
let file_type = entry.file_type().map_err(|_| filesystem_failed())?;
if !file_type.is_dir() || file_type.is_symlink() {
continue;
}
let directory_name = match entry.file_name().into_string() {
Ok(name) if valid_skill_name(&name) => name,
_ => continue,
};
let skill_dir = match entry.open_dir() {
Ok(directory) => directory,
Err(_) => continue,
};
if let Some(item) =
self.installed_from_open_directory(root, &directory_name, &skill_dir)?
{
installed.push(item);
}
}
Ok(())
}
fn installed_from_open_directory(
&self,
root: &OpenedSkillRoot,
directory_name: &str,
skill_dir: &Dir,
) -> AgentResult<Option<InstalledSkill>> {
let metadata = match skill_dir.symlink_metadata("SKILL.md") {
Ok(metadata) if metadata.file_type().is_file() => metadata,
_ => return Ok(None),
};
if metadata.len() > MAX_FILE_BYTES as u64 {
return Ok(None);
}
let bytes = read_bounded_file(skill_dir, "SKILL.md", MAX_FILE_BYTES)?;
let manifest = read_manifest(skill_dir)?.filter(|manifest| {
manifest_matches_location(manifest, &root.scope, root.workspace.as_deref())
});
let skill_metadata = if manifest.is_some() {
match parse_skill_metadata(&bytes) {
Ok(metadata) => metadata,
Err(_) => return Ok(None),
}
} else {
let Some((name, description)) = parse_lenient_skill_metadata(&bytes) else {
return Ok(None);
};
SkillMetadata {
name,
description,
compatibility: None,
}
};
let managed = manifest.is_some();
let locator = InstallationLocator {
version: LOCATOR_VERSION,
scope: root.scope.clone(),
workspace: root.workspace.clone(),
name: directory_name.to_owned(),
};
Ok(Some(InstalledSkill {
installation_id: encode_locator(&locator)?,
name: skill_metadata.name,
description: skill_metadata.description,
provider: manifest.as_ref().map(|manifest| manifest.provider.clone()),
skill_id: manifest.as_ref().map(|manifest| manifest.skill_id.clone()),
source: manifest.as_ref().map(|manifest| manifest.source.clone()),
scope: root.scope.clone(),
workspace: root.workspace.clone(),
managed,
content_hash: manifest
.as_ref()
.map(|manifest| manifest.content_hash.clone()),
installed_at: manifest
.as_ref()
.map(|manifest| manifest.installed_at.clone()),
origin: if managed {
SkillOrigin::Regy
} else {
SkillOrigin::Pi
},
}))
}
fn listed_from_open_root(&self, root: &OpenedSkillRoot) -> AgentResult<Vec<InstalledSkill>> {
let mut installed = Vec::new();
self.scan_open_root(root, &mut installed)?;
installed.sort_by(|left, right| left.installation_id.cmp(&right.installation_id));
Ok(installed)
}
fn discovered_by_directory_name(
&self,
root: &OpenedSkillRoot,
directory_name: &str,
) -> AgentResult<Option<InstalledSkill>> {
let installation_id = encode_locator(&InstallationLocator {
version: LOCATOR_VERSION,
scope: root.scope.clone(),
workspace: root.workspace.clone(),
name: directory_name.to_owned(),
})?;
Ok(self
.listed_from_open_root(root)?
.into_iter()
.find(|item| item.installation_id == installation_id))
}
fn prepare_root(&self, location: &InstallLocation) -> AgentResult<OpenedSkillRoot> {
if let Some(workspace) = location.workspace.as_deref() {
let expected = ambient_directory_identity(Path::new(workspace))?;
self.root_opened_hook
.after_root_validated_before_open(SkillScope::Workspace);
let workspace_dir = Dir::open_ambient_dir(workspace, ambient_authority())
.map_err(|_| scope_denied())?;
verify_directory_identity(&workspace_dir, expected)?;
let agents = create_or_open_child(&workspace_dir, ".agents")?;
let dir = create_or_open_child(&agents, "skills")?;
let root = OpenedSkillRoot {
dir,
scope: SkillScope::Workspace,
workspace: Some(workspace.to_owned()),
};
self.root_opened_hook
.after_skill_root_opened(root.scope.clone());
Ok(root)
} else {
let (parent, child) = global_parent_and_child(&location.root)?;
Dir::create_ambient_dir_all(&parent, ambient_authority())
.map_err(|_| filesystem_failed())?;
let parent = parent.canonicalize().map_err(|_| scope_denied())?;
let expected = ambient_directory_identity(&parent)?;
self.root_opened_hook
.after_root_validated_before_open(SkillScope::Global);
let parent_dir =
Dir::open_ambient_dir(&parent, ambient_authority()).map_err(|_| scope_denied())?;
verify_directory_identity(&parent_dir, expected)?;
let dir = create_or_open_child(&parent_dir, &child)?;
let root = OpenedSkillRoot {
dir,
scope: SkillScope::Global,
workspace: None,
};
self.root_opened_hook
.after_skill_root_opened(root.scope.clone());
Ok(root)
}
}
fn open_existing_root(
&self,
root: &Path,
scope: SkillScope,
workspace: Option<String>,
) -> AgentResult<Option<OpenedSkillRoot>> {
let dir = match scope {
SkillScope::Global => {
let (parent, child) = global_parent_and_child(root)?;
let parent = match parent.canonicalize() {
Ok(parent) => parent,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(_) => return Err(scope_denied()),
};
let expected = ambient_directory_identity(&parent)?;
self.root_opened_hook
.after_root_validated_before_open(SkillScope::Global);
let parent_dir = Dir::open_ambient_dir(&parent, ambient_authority())
.map_err(|_| scope_denied())?;
verify_directory_identity(&parent_dir, expected)?;
let Some(dir) = open_existing_child(&parent_dir, &child)? else {
return Ok(None);
};
dir
}
SkillScope::Workspace => {
let workspace_path = workspace.as_deref().ok_or_else(scope_denied)?;
let expected = ambient_directory_identity(Path::new(workspace_path))?;
self.root_opened_hook
.after_root_validated_before_open(SkillScope::Workspace);
let workspace_dir = Dir::open_ambient_dir(workspace_path, ambient_authority())
.map_err(|_| scope_denied())?;
verify_directory_identity(&workspace_dir, expected)?;
let Some(agents) = open_existing_child(&workspace_dir, ".agents")? else {
return Ok(None);
};
let Some(skills) = open_existing_child(&agents, "skills")? else {
return Ok(None);
};
skills
}
};
let opened = OpenedSkillRoot {
dir,
scope,
workspace,
};
self.root_opened_hook
.after_skill_root_opened(opened.scope.clone());
Ok(Some(opened))
}
}
#[derive(Debug, Clone)]
struct InstallLocation {
root: PathBuf,
workspace: Option<String>,
}
struct DiscoveryRoot {
path: PathBuf,
accept_root_markdown: bool,
scope: SkillScope,
workspace: Option<String>,
}
struct ListedDiscovery {
canonical_source: Option<PathBuf>,
item: InstalledSkill,
}
pub(crate) struct WorkspacePiCommands {
pub(crate) workspace: PathBuf,
pub(crate) commands: Vec<PiCommandInfo>,
}
struct OpenedSkillRoot {
dir: Dir,
scope: SkillScope,
workspace: Option<String>,
}
struct Replacement {
backup: Option<QuarantinedChild>,
activated_identity: DirectoryIdentity,
}
struct QuarantinedChild {
name: String,
dir: Dir,
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct DirectoryIdentity {
device: u64,
inode: u64,
}
enum SourcePayload {
Files {
files: Vec<SkillFile>,
revision: String,
},
Archive {
bytes: Vec<u8>,
directory: String,
revision: String,
},
}
#[derive(Debug)]
struct ValidatedFiles {
files: Vec<SkillFile>,
metadata: SkillMetadata,
}
#[derive(Debug, Deserialize)]
struct SkillMetadata {
name: String,
description: String,
compatibility: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct ManagedSkillManifest {
schema_version: u8,
provider: String,
skill_id: String,
source: String,
revision: String,
content_hash: String,
scope: SkillScope,
workspace: Option<String>,
installed_at: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct InstallationLocator {
version: u8,
scope: SkillScope,
workspace: Option<String>,
name: String,
}
pub(crate) trait DirectoryOperations: Send + Sync {
fn rename_noreplace(&self, root: &Dir, from: &Path, to: &Path) -> io::Result<()> {
rename_noreplace(root, from, to)
}
fn remove_open_dir_all(&self, directory: Dir) -> io::Result<()> {
directory.remove_open_dir_all()
}
}
struct StdDirectoryOperations;
impl DirectoryOperations for StdDirectoryOperations {}
pub(crate) trait RootOpenedHook: Send + Sync {
fn after_root_validated_before_open(&self, _scope: SkillScope) {}
fn after_skill_root_opened(&self, _scope: SkillScope) {}
fn before_destination_quarantine(&self, _scope: SkillScope, _destination: &str) {}
fn after_destination_quarantined(&self, _scope: SkillScope, _quarantine: &str) {}
fn after_cleanup_handle_opened(&self, _scope: SkillScope, _cleanup_name: &str) {}
}
struct NoopRootOpenedHook;
impl RootOpenedHook for NoopRootOpenedHook {
fn after_skill_root_opened(&self, _scope: SkillScope) {}
}
#[cfg(any(target_vendor = "apple", target_os = "linux"))]
fn rename_noreplace(root: &Dir, from: &Path, to: &Path) -> io::Result<()> {
renameat_with(root.as_fd(), from, root.as_fd(), to, RenameFlags::NOREPLACE).map_err(Into::into)
}
#[cfg(not(any(target_vendor = "apple", target_os = "linux")))]
fn rename_noreplace(_root: &Dir, _from: &Path, _to: &Path) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"atomic no-replace rename is unavailable",
))
}
fn validate_files(files: Vec<SkillFile>) -> AgentResult<ValidatedFiles> {
if files.is_empty() || files.len() > MAX_FILES {
return Err(skill_invalid());
}
let mut seen = HashSet::with_capacity(files.len());
let mut total = 0usize;
let mut skill_markdown = None;
for file in &files {
let path = validated_relative_path(&file.path)?;
if path == Path::new(MANIFEST_NAME) || !seen.insert(path.clone()) {
return Err(skill_invalid());
}
if file.bytes.len() > MAX_FILE_BYTES {
return Err(skill_invalid());
}
total = total
.checked_add(file.bytes.len())
.filter(|total| *total <= MAX_TOTAL_BYTES)
.ok_or_else(skill_invalid)?;
if path == Path::new("SKILL.md") {
skill_markdown = Some(file.bytes.as_slice());
}
}
let metadata = parse_skill_metadata(skill_markdown.ok_or_else(skill_invalid)?)?;
Ok(ValidatedFiles { files, metadata })
}
fn parse_skill_metadata(bytes: &[u8]) -> AgentResult<SkillMetadata> {
let yaml = super::pi_skill_discovery::frontmatter(bytes).ok_or_else(skill_invalid)?;
let mut metadata: SkillMetadata =
serde_yaml_ng::from_str(&yaml).map_err(|_| skill_invalid())?;
metadata.name = metadata.name.trim().to_owned();
metadata.description = metadata.description.trim().to_owned();
metadata.compatibility = metadata.compatibility.map(|value| value.trim().to_owned());
if !valid_skill_name(&metadata.name)
|| metadata.description.is_empty()
|| metadata.description.len() > 1024
|| metadata
.compatibility
.as_ref()
.is_some_and(|compatibility| compatibility.len() > 500)
{
return Err(skill_invalid());
}
Ok(metadata)
}
fn valid_skill_name(name: &str) -> bool {
!name.is_empty()
&& name.len() <= 64
&& !name.starts_with('-')
&& !name.ends_with('-')
&& !name.contains("--")
&& name
.bytes()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
}
fn validated_relative_path(raw: &str) -> AgentResult<PathBuf> {
if raw.is_empty() || raw.contains('\0') || raw.contains('\\') {
return Err(skill_invalid());
}
let path = Path::new(raw);
if path.is_absolute()
|| path.components().any(
|component| !matches!(component, Component::Normal(segment) if !segment.is_empty()),
)
{
return Err(skill_invalid());
}
Ok(path.to_path_buf())
}
fn write_staged(staged: &Dir, files: &[SkillFile], manifest_bytes: &[u8]) -> AgentResult<()> {
for skill_file in files {
let relative = validated_relative_path(&skill_file.path)?;
if let Some(parent) = relative.parent() {
staged
.create_dir_all(parent)
.map_err(|_| filesystem_failed())?;
}
let mut options = OpenOptions::new();
options.write(true).create_new(true);
set_create_mode(&mut options, skill_file.executable);
let mut file = staged
.open_with(&relative, &options)
.map_err(|_| filesystem_failed())?;
file.write_all(&skill_file.bytes)
.map_err(|_| filesystem_failed())?;
set_file_permissions(&file, skill_file.executable)?;
file.sync_all().map_err(|_| filesystem_failed())?;
}
let mut options = OpenOptions::new();
options.write(true).create_new(true);
set_create_mode(&mut options, false);
let mut file = staged
.open_with(MANIFEST_NAME, &options)
.map_err(|_| filesystem_failed())?;
file.write_all(manifest_bytes)
.map_err(|_| filesystem_failed())?;
set_file_permissions(&file, false)?;
file.sync_all().map_err(|_| filesystem_failed())
}
fn serialize_manifest(manifest: &ManagedSkillManifest) -> AgentResult<Vec<u8>> {
let bytes = serde_json::to_vec_pretty(manifest).map_err(|_| filesystem_failed())?;
if bytes.len() > MAX_MANIFEST_BYTES as usize {
return Err(skill_invalid());
}
Ok(bytes)
}
#[cfg(unix)]
fn set_create_mode(options: &mut OpenOptions, executable: bool) {
use cap_std::fs::OpenOptionsExt;
let mode = if executable { 0o755 } else { 0o644 };
options.mode(mode);
}
#[cfg(not(unix))]
fn set_create_mode(_options: &mut OpenOptions, _executable: bool) {}
#[cfg(unix)]
fn set_file_permissions(file: &cap_std::fs::File, executable: bool) -> AgentResult<()> {
use cap_std::fs::PermissionsExt;
let mode = if executable { 0o755 } else { 0o644 };
file.set_permissions(cap_std::fs::Permissions::from_mode(mode))
.map_err(|_| filesystem_failed())
}
#[cfg(not(unix))]
fn set_file_permissions(_file: &cap_std::fs::File, _executable: bool) -> AgentResult<()> {
Ok(())
}
fn content_hash(files: &[SkillFile]) -> String {
let mut sorted: Vec<_> = files.iter().collect();
sorted.sort_by(|left, right| left.path.cmp(&right.path));
let mut digest = Sha256::new();
for file in sorted {
digest.update((file.path.len() as u64).to_be_bytes());
digest.update(file.path.as_bytes());
digest.update((file.bytes.len() as u64).to_be_bytes());
digest.update(&file.bytes);
}
let digest = digest.finalize();
let mut encoded = String::with_capacity(7 + digest.len() * 2);
encoded.push_str("sha256:");
for byte in digest {
write!(&mut encoded, "{byte:02x}").expect("writing to a String cannot fail");
}
encoded
}
fn read_manifest(directory: &Dir) -> AgentResult<Option<ManagedSkillManifest>> {
let metadata = match directory.symlink_metadata(MANIFEST_NAME) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(_) => return Err(filesystem_failed()),
};
if !metadata.file_type().is_file() || metadata.len() > MAX_MANIFEST_BYTES {
return Ok(None);
}
let bytes = read_bounded_file(directory, MANIFEST_NAME, MAX_MANIFEST_BYTES as usize)?;
let manifest: ManagedSkillManifest = match serde_json::from_slice(&bytes) {
Ok(manifest) => manifest,
Err(_) => return Ok(None),
};
if manifest.schema_version != MANIFEST_SCHEMA_VERSION
|| manifest.provider.trim().is_empty()
|| manifest.skill_id.trim().is_empty()
|| !manifest.content_hash.starts_with("sha256:")
{
return Ok(None);
}
Ok(Some(manifest))
}
fn read_manifest_path(directory: &Path) -> AgentResult<Option<ManagedSkillManifest>> {
let path = directory.join(MANIFEST_NAME);
let metadata = match fs::symlink_metadata(&path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(_) => return Err(filesystem_failed()),
};
if metadata.file_type().is_symlink()
|| !metadata.file_type().is_file()
|| metadata.len() > MAX_MANIFEST_BYTES
{
return Ok(None);
}
let bytes = read_bounded_path(&path, MAX_MANIFEST_BYTES as usize)?;
let manifest: ManagedSkillManifest = match serde_json::from_slice(&bytes) {
Ok(manifest) => manifest,
Err(_) => return Ok(None),
};
if manifest.schema_version != MANIFEST_SCHEMA_VERSION
|| manifest.provider.trim().is_empty()
|| manifest.skill_id.trim().is_empty()
|| !manifest.content_hash.starts_with("sha256:")
{
return Ok(None);
}
Ok(Some(manifest))
}
fn manifest_matches_location(
manifest: &ManagedSkillManifest,
scope: &SkillScope,
workspace: Option<&str>,
) -> bool {
manifest.scope == *scope && manifest.workspace.as_deref() == workspace
}
fn manifest_matches_installed_record(
manifest: &ManagedSkillManifest,
installed: &InstalledSkill,
) -> bool {
installed.managed
&& installed.provider.as_deref() == Some(manifest.provider.as_str())
&& installed.skill_id.as_deref() == Some(manifest.skill_id.as_str())
&& installed.content_hash.as_deref() == Some(manifest.content_hash.as_str())
&& installed.scope == manifest.scope
&& installed.workspace == manifest.workspace
}
fn encode_locator(locator: &InstallationLocator) -> AgentResult<String> {
let bytes = serde_json::to_vec(locator).map_err(|_| filesystem_failed())?;
Ok(URL_SAFE_NO_PAD.encode(bytes))
}
fn pi_installation_id(path: &Path) -> String {
let mut digest = Sha256::new();
digest.update(path.as_os_str().as_encoded_bytes());
let digest = digest.finalize();
let mut id = String::from("pi:");
for byte in digest {
write!(&mut id, "{byte:02x}").expect("writing to a String cannot fail");
}
id
}
fn pi_command_installation_id(workspace: &Path, name: &str) -> String {
let mut digest = Sha256::new();
digest.update(workspace.as_os_str().as_encoded_bytes());
digest.update([0]);
digest.update(name.as_bytes());
let digest = digest.finalize();
let mut id = String::from("pi:");
for byte in digest {
write!(&mut id, "{byte:02x}").expect("writing to a String cannot fail");
}
id
}
fn runtime_discovery(workspace: &Path, command: PiCommandInfo) -> Option<ListedDiscovery> {
if command.source != "skill" {
return None;
}
let name = command.name.strip_prefix("skill:")?.trim().to_owned();
let description = command.description?.trim().to_owned();
if name.is_empty() || description.is_empty() {
return None;
}
let canonical_source = match command.path.as_deref() {
Some(path) => {
let path = Path::new(path);
let metadata = fs::symlink_metadata(path).ok()?;
if metadata.file_type().is_symlink() || !metadata.file_type().is_file() {
return None;
}
Some(path.canonicalize().ok()?)
}
None => None,
};
let workspace_scope = canonical_source
.as_ref()
.is_some_and(|path| path.starts_with(workspace));
let scope = if workspace_scope {
SkillScope::Workspace
} else {
SkillScope::Global
};
let item_workspace = workspace_scope.then(|| workspace.to_string_lossy().into_owned());
let installation_id = canonical_source.as_ref().map_or_else(
|| pi_command_installation_id(workspace, &name),
|path| pi_installation_id(path),
);
Some(ListedDiscovery {
canonical_source,
item: InstalledSkill {
installation_id,
name,
description,
provider: None,
skill_id: None,
source: None,
scope,
workspace: item_workspace,
managed: false,
content_hash: None,
installed_at: None,
origin: SkillOrigin::Pi,
},
})
}
fn finalize_discoveries(mut discovered: Vec<ListedDiscovery>) -> Vec<InstalledSkill> {
discovered.sort_by(|left, right| {
right
.item
.managed
.cmp(&left.item.managed)
.then_with(|| left.canonical_source.cmp(&right.canonical_source))
});
let mut canonical_sources = HashSet::new();
let mut names = HashSet::new();
let mut installed = discovered
.into_iter()
.filter(|discovery| {
discovery
.canonical_source
.as_ref()
.is_none_or(|path| canonical_sources.insert(path.clone()))
})
.filter(|discovery| names.insert(discovery.item.name.clone()))
.map(|discovery| discovery.item)
.collect::<Vec<_>>();
installed.sort_by(|left, right| {
let left_key = (
format!("{:?}", left.scope),
left.name.clone(),
left.workspace.clone(),
);
let right_key = (
format!("{:?}", right.scope),
right.name.clone(),
right.workspace.clone(),
);
left_key.cmp(&right_key)
});
let mut response_bytes = 0usize;
installed.retain(|item| {
let item_bytes = item.name.len()
+ item.description.len()
+ item.installation_id.len()
+ item.workspace.as_ref().map_or(0, String::len);
response_bytes = response_bytes.saturating_add(item_bytes);
response_bytes <= MAX_RESPONSE_BYTES
});
installed.truncate(MAX_LISTED_SKILLS);
installed
}
fn decode_locator(encoded: &str) -> AgentResult<InstallationLocator> {
if encoded.is_empty() || encoded.len() > MAX_LOCATOR_BYTES {
return Err(skill_not_found());
}
let bytes = URL_SAFE_NO_PAD
.decode(encoded)
.map_err(|_| skill_not_found())?;
if URL_SAFE_NO_PAD.encode(&bytes) != encoded {
return Err(skill_not_found());
}
serde_json::from_slice(&bytes).map_err(|_| skill_not_found())
}
fn validate_locator(locator: &InstallationLocator) -> AgentResult<()> {
if locator.version != LOCATOR_VERSION || !valid_skill_name(&locator.name) {
return Err(skill_not_found());
}
match locator.scope {
SkillScope::Global if locator.workspace.is_none() => Ok(()),
SkillScope::Workspace
if locator
.workspace
.as_deref()
.is_some_and(|path| !path.is_empty()) =>
{
Ok(())
}
_ => Err(skill_not_found()),
}
}
fn create_or_open_child(parent: &Dir, name: impl AsRef<Path>) -> AgentResult<Dir> {
let name = name.as_ref();
match parent.create_dir(name) {
Ok(()) => {}
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}
Err(_) => return Err(filesystem_failed()),
}
let metadata = parent
.symlink_metadata(name)
.map_err(|_| filesystem_failed())?;
if !metadata.file_type().is_dir() || metadata.file_type().is_symlink() {
return Err(scope_denied());
}
let expected = capability_metadata_identity(&metadata)?;
let child = parent.open_dir(name).map_err(|_| scope_denied())?;
verify_directory_identity(&child, expected)?;
Ok(child)
}
fn open_existing_child(parent: &Dir, name: impl AsRef<Path>) -> AgentResult<Option<Dir>> {
let name = name.as_ref();
let metadata = match parent.symlink_metadata(name) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(_) => return Err(filesystem_failed()),
};
if !metadata.file_type().is_dir() || metadata.file_type().is_symlink() {
return Err(scope_denied());
}
let expected = capability_metadata_identity(&metadata)?;
let child = parent.open_dir(name).map_err(|_| scope_denied())?;
verify_directory_identity(&child, expected)?;
Ok(Some(child))
}
fn global_parent_and_child(root: &Path) -> AgentResult<(PathBuf, PathBuf)> {
let child = root.file_name().ok_or_else(scope_denied)?;
if !matches!(
Path::new(child).components().next(),
Some(Component::Normal(_))
) {
return Err(scope_denied());
}
let parent = root.parent().ok_or_else(scope_denied)?;
Ok((parent.to_path_buf(), PathBuf::from(child)))
}
#[cfg(unix)]
fn ambient_directory_identity(path: &Path) -> AgentResult<DirectoryIdentity> {
let metadata = fs::metadata(path).map_err(|_| scope_denied())?;
if !metadata.is_dir() {
return Err(scope_denied());
}
Ok(DirectoryIdentity {
device: StdMetadataExt::dev(&metadata),
inode: StdMetadataExt::ino(&metadata),
})
}
#[cfg(not(unix))]
fn ambient_directory_identity(path: &Path) -> AgentResult<DirectoryIdentity> {
if !fs::metadata(path).map_err(|_| scope_denied())?.is_dir() {
return Err(scope_denied());
}
Ok(DirectoryIdentity {
device: 0,
inode: 0,
})
}
#[cfg(unix)]
fn capability_metadata_identity(
metadata: &cap_std::fs::Metadata,
) -> AgentResult<DirectoryIdentity> {
Ok(DirectoryIdentity {
device: CapMetadataExt::dev(metadata),
inode: CapMetadataExt::ino(metadata),
})
}
#[cfg(not(unix))]
fn capability_metadata_identity(
_metadata: &cap_std::fs::Metadata,
) -> AgentResult<DirectoryIdentity> {
Ok(DirectoryIdentity {
device: 0,
inode: 0,
})
}
fn verify_directory_identity(directory: &Dir, expected: DirectoryIdentity) -> AgentResult<()> {
let metadata = directory.dir_metadata().map_err(|_| scope_denied())?;
if capability_metadata_identity(&metadata)? != expected {
return Err(scope_denied());
}
Ok(())
}
fn read_bounded_file(
directory: &Dir,
path: impl AsRef<Path>,
limit: usize,
) -> AgentResult<Vec<u8>> {
let mut bytes = Vec::new();
directory
.open(path)
.map_err(|_| filesystem_failed())?
.take((limit + 1) as u64)
.read_to_end(&mut bytes)
.map_err(|_| filesystem_failed())?;
if bytes.len() > limit {
return Err(skill_invalid());
}
Ok(bytes)
}
fn read_bounded_path(path: &Path, limit: usize) -> AgentResult<Vec<u8>> {
let metadata = fs::symlink_metadata(path).map_err(|_| filesystem_failed())?;
if metadata.file_type().is_symlink() || !metadata.file_type().is_file() {
return Err(filesystem_failed());
}
let mut bytes = Vec::new();
fs::File::open(path)
.map_err(|_| filesystem_failed())?
.take((limit + 1) as u64)
.read_to_end(&mut bytes)
.map_err(|_| filesystem_failed())?;
if bytes.len() > limit {
return Err(skill_invalid());
}
Ok(bytes)
}
#[cfg(test)]
fn test_home_root(global_root: &Path) -> PathBuf {
global_root
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| global_root.to_path_buf())
}
fn skill_invalid() -> AgentError {
AgentError::new(ErrorCode::SkillInvalid, "skill is invalid")
}
fn skill_conflict() -> AgentError {
AgentError::new(ErrorCode::SkillConflict, "skill destination conflicts")
}
fn skill_not_found() -> AgentError {
AgentError::new(ErrorCode::SkillNotFound, "skill installation not found")
}
fn scope_denied() -> AgentError {
AgentError::new(ErrorCode::SkillScopeDenied, "workspace denied")
}
fn filesystem_failed() -> AgentError {
AgentError::new(
ErrorCode::SkillFilesystemFailed,
"skill filesystem operation failed",
)
}