"""Observe shared skill edits before accepting a new model run.""" import asyncio import time class SharedSkillRefresh: def __init__(self): self.lock = asyncio.Lock() self.extensions = None self.skills = None self.checked_at = 0.0 @staticmethod def extension_stamp(): from deerflow.config.extensions_config import ExtensionsConfig path = ExtensionsConfig.resolve_config_path() if path is None: return None stat = path.stat() return (str(path), stat.st_mtime_ns, stat.st_size) @staticmethod def skill_stamp(): from pathlib import Path from deerflow.skills.storage import get_or_new_skill_storage root = Path(get_or_new_skill_storage().get_skills_root_path()) stamps = [] for category in ("public", "custom"): for path in (root / category).rglob("*"): if path.suffix in {".md", ".py"} and path.is_file(): stat = path.stat() stamps.append((str(path), stat.st_mtime_ns, stat.st_size)) return tuple(sorted(stamps)) async def check(self): async with self.lock: extension = await asyncio.to_thread(self.extension_stamp) skills = self.skills if time.monotonic() - self.checked_at >= 5: skills = await asyncio.to_thread(self.skill_stamp) self.checked_at = time.monotonic() if extension != self.extensions or skills != self.skills: from deerflow.agents.lead_agent.prompt import refresh_skills_system_prompt_cache_async from deerflow.config.extensions_config import reload_extensions_config await asyncio.to_thread(reload_extensions_config) await refresh_skills_system_prompt_cache_async() self.extensions, self.skills = extension, skills