fix: expand skill bundles in cron jobs
This commit is contained in:
@ -1115,10 +1115,36 @@ def _build_job_prompt(job: dict, prerun_script: Optional[tuple] = None) -> str:
|
||||
|
||||
from tools.skills_tool import skill_view
|
||||
from tools.skill_usage import bump_use
|
||||
from agent.skill_bundles import build_bundle_invocation_message, resolve_bundle_command_key
|
||||
|
||||
parts = []
|
||||
skipped: list[str] = []
|
||||
for skill_name in skill_names:
|
||||
# Cron jobs historically accepted only skill names here, but the CLI/gateway
|
||||
# slash-command path lets bundles shadow skills with the same slug. Mirror
|
||||
# that behavior so `skills: ["my-bundle"]` expands bundle members instead
|
||||
# of being treated as a missing skill.
|
||||
bundle_key = resolve_bundle_command_key(skill_name.lstrip("/"))
|
||||
if bundle_key:
|
||||
bundle_payload = build_bundle_invocation_message(
|
||||
bundle_key,
|
||||
user_instruction="",
|
||||
task_id=str(job.get("id") or "") or None,
|
||||
)
|
||||
if bundle_payload:
|
||||
bundle_message, _loaded_bundle_skills, _missing_bundle_skills = bundle_payload
|
||||
if parts:
|
||||
parts.append("")
|
||||
parts.append(bundle_message)
|
||||
continue
|
||||
logger.warning(
|
||||
"Cron job '%s': bundle '%s' could not load any skills, skipping",
|
||||
job.get("name", job.get("id")),
|
||||
skill_name,
|
||||
)
|
||||
skipped.append(skill_name)
|
||||
continue
|
||||
|
||||
try:
|
||||
loaded = json.loads(skill_view(skill_name))
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
|
||||
@ -41,6 +41,7 @@ def cron_env(tmp_path, monkeypatch):
|
||||
(hermes_home / "cron").mkdir()
|
||||
(hermes_home / "cron" / "output").mkdir()
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
monkeypatch.setenv("HERMES_BUNDLES_DIR", str(hermes_home / "skill-bundles"))
|
||||
|
||||
# Patch the module-level SKILLS_DIR snapshots that `skill_view()`
|
||||
# uses. Without this, the tool resolves against the real
|
||||
@ -49,6 +50,11 @@ def cron_env(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(_skills_tool, "SKILLS_DIR", skills_dir)
|
||||
monkeypatch.setattr(_skills_tool, "HERMES_HOME", hermes_home)
|
||||
|
||||
# Reset bundle cache and make bundle discovery hit this test home.
|
||||
import agent.skill_bundles as _skill_bundles
|
||||
_skill_bundles._bundles_cache = {}
|
||||
_skill_bundles._bundles_cache_mtime = None
|
||||
|
||||
# Return both the home dir and the scheduler module so tests use the
|
||||
# CURRENT module object (post any reload that happened in fixtures of
|
||||
# previously-executed tests in the same worker).
|
||||
@ -66,6 +72,20 @@ def _plant_skill(hermes_home: Path, name: str, body: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _plant_bundle(hermes_home: Path, name: str, skills: list[str], instruction: str = "") -> None:
|
||||
"""Drop a bundle YAML into ~/.hermes/skill-bundles/ and refresh cache."""
|
||||
bundles_dir = hermes_home / "skill-bundles"
|
||||
bundles_dir.mkdir(parents=True, exist_ok=True)
|
||||
lines = [f"name: {name}", "skills:"]
|
||||
lines.extend(f" - {skill}" for skill in skills)
|
||||
if instruction:
|
||||
lines.append("instruction: |")
|
||||
lines.extend(f" {line}" for line in instruction.splitlines())
|
||||
(bundles_dir / f"{name}.yaml").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
import agent.skill_bundles as _skill_bundles
|
||||
_skill_bundles.scan_bundles()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _scan_assembled_cron_prompt — isolated unit
|
||||
# ---------------------------------------------------------------------------
|
||||
@ -255,3 +275,47 @@ class TestBuildJobPromptScansSkillContent:
|
||||
prompt = scheduler._build_job_prompt(job)
|
||||
assert prompt is not None
|
||||
assert "could not be found" in prompt
|
||||
|
||||
def test_skill_bundle_in_job_skills_loads_referenced_skills(self, cron_env):
|
||||
hermes_home, scheduler = cron_env
|
||||
_plant_skill(hermes_home, "alpha-skill", "Alpha guidance for the cron task.")
|
||||
_plant_skill(hermes_home, "beta-skill", "Beta guidance for the cron task.")
|
||||
_plant_bundle(
|
||||
hermes_home,
|
||||
"article-pipeline",
|
||||
["alpha-skill", "beta-skill"],
|
||||
instruction="Use the skills in order.",
|
||||
)
|
||||
|
||||
job = {
|
||||
"id": "job-bundle",
|
||||
"name": "bundle cron",
|
||||
"prompt": "write the report",
|
||||
"skills": ["article-pipeline"],
|
||||
}
|
||||
|
||||
prompt = scheduler._build_job_prompt(job)
|
||||
assert prompt is not None
|
||||
assert '"article-pipeline" skill bundle' in prompt
|
||||
assert "Alpha guidance for the cron task." in prompt
|
||||
assert "Beta guidance for the cron task." in prompt
|
||||
assert "Bundle instruction: Use the skills in order." in prompt
|
||||
assert "skill(s) were listed for this job but could not be found" not in prompt
|
||||
|
||||
def test_bundle_name_shadows_skill_name_for_cron_jobs(self, cron_env):
|
||||
hermes_home, scheduler = cron_env
|
||||
_plant_skill(hermes_home, "article-pipeline", "Standalone skill should not win.")
|
||||
_plant_skill(hermes_home, "bundle-member", "Bundle member should win.")
|
||||
_plant_bundle(hermes_home, "article-pipeline", ["bundle-member"])
|
||||
|
||||
job = {
|
||||
"id": "job-bundle-shadow",
|
||||
"name": "bundle shadows skill",
|
||||
"prompt": "run",
|
||||
"skills": ["article-pipeline"],
|
||||
}
|
||||
|
||||
prompt = scheduler._build_job_prompt(job)
|
||||
assert prompt is not None
|
||||
assert "Bundle member should win." in prompt
|
||||
assert "Standalone skill should not win." not in prompt
|
||||
|
||||
Reference in New Issue
Block a user