c47e6445d0
CI / quality (pull_request) Successful in 19s
CI / lint (pull_request) Successful in 22s
CI / benchmark-publish (pull_request) Has been skipped
CI / security (pull_request) Successful in 33s
CI / build (pull_request) Successful in 26s
CI / typecheck (pull_request) Successful in 1m0s
CI / integration_tests (pull_request) Successful in 4m56s
CI / unit_tests (pull_request) Successful in 15m11s
CI / docker (pull_request) Successful in 1m33s
CI / benchmark-regression (pull_request) Successful in 20m55s
CI / coverage (pull_request) Successful in 33m42s
CI / lint (push) Successful in 13s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 17s
CI / security (push) Successful in 28s
CI / typecheck (push) Successful in 32s
CI / benchmark-regression (push) Has been skipped
CI / integration_tests (push) Successful in 3m3s
CI / benchmark-publish (push) Successful in 10m8s
CI / unit_tests (push) Successful in 12m42s
CI / docker (push) Successful in 39s
CI / coverage (push) Has been cancelled
Add ActorCompiler module that translates GRAPH-type ActorConfigSchema definitions into LangGraph NodeConfig/Edge structures with LSP binding metadata. Includes subgraph resolution with cross-actor cycle detection, entry/exit validation, and CompilationMetadata for diagnostics. New files: - src/cleveragents/actor/compiler.py: Core compiler with compile_actor() - features/actor_compiler.feature: 13 Behave scenarios - features/steps/actor_compiler_steps.py: Step definitions - robot/actor_compiler.robot: 4 Robot smoke tests - benchmarks/actor_compiler_bench.py: ASV performance benchmarks - docs/reference/actor_compiler.md: Compilation pipeline reference Modified: - src/cleveragents/actor/__init__.py: Export compiler types - vulture_whitelist.py: Whitelist new public API ISSUES CLOSED: #158
148 lines
4.2 KiB
Python
148 lines
4.2 KiB
Python
"""Robot Framework helper for Agent Skills discovery tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from cleveragents.application.services.config_service import ConfigService
|
|
from cleveragents.skills.discovery import (
|
|
DiscoveredAgentSkill,
|
|
build_tool_spec,
|
|
register_discovered_skills,
|
|
scan_directory,
|
|
)
|
|
from cleveragents.skills.discovery import (
|
|
parse_agent_skills_paths as _parse_paths,
|
|
)
|
|
from cleveragents.tool.registry import ToolRegistry
|
|
from cleveragents.tool.runtime import ToolSpec
|
|
|
|
|
|
def _noop_handler(**_kwargs: Any) -> dict[str, Any]:
|
|
return {"status": "placeholder"}
|
|
|
|
|
|
def _create_skill_md(folder: Path, name: str, description: str) -> None:
|
|
folder.mkdir(parents=True, exist_ok=True)
|
|
content = (
|
|
"---\n"
|
|
f"name: {name}\n"
|
|
f"description: {description}\n"
|
|
"---\n"
|
|
f"\n# {name}\n\n{description}\n"
|
|
)
|
|
(folder / "SKILL.md").write_text(content, encoding="utf-8")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Robot keywords
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def parse_agent_skills_paths(raw_value: str) -> list[str]:
|
|
"""Parse comma-separated paths and return as string list."""
|
|
paths = _parse_paths(raw_value)
|
|
return [str(p) for p in paths]
|
|
|
|
|
|
def create_agent_skills_directory(count: int) -> str:
|
|
"""Create a temp directory with *count* agent skill folders."""
|
|
tmpdir = Path(tempfile.mkdtemp())
|
|
for i in range(int(count)):
|
|
_create_skill_md(tmpdir / f"skill-{i}", f"skill-{i}", f"Skill {i}")
|
|
return str(tmpdir)
|
|
|
|
|
|
def create_mixed_directory() -> str:
|
|
"""Create a directory with one valid skill and one without SKILL.md."""
|
|
tmpdir = Path(tempfile.mkdtemp())
|
|
_create_skill_md(tmpdir / "valid-skill", "valid-skill", "A valid skill")
|
|
(tmpdir / "no-skill").mkdir()
|
|
return str(tmpdir)
|
|
|
|
|
|
def scan_agent_skills_directory(directory: str) -> list[dict[str, Any]]:
|
|
"""Scan a directory and return discovered skills as dicts."""
|
|
results = scan_directory(Path(directory))
|
|
return [
|
|
{"name": s.name, "description": s.description, "path": s.path} for s in results
|
|
]
|
|
|
|
|
|
def build_agent_skill_toolspec(name: str, path: str) -> ToolSpec:
|
|
"""Build a ToolSpec from a discovered agent skill."""
|
|
skill = DiscoveredAgentSkill(
|
|
name=name,
|
|
description=f"Agent skill {name}",
|
|
path=path,
|
|
)
|
|
return build_tool_spec(skill)
|
|
|
|
|
|
def register_agent_skills_in_fresh_registry(
|
|
count: int,
|
|
) -> dict[str, int]:
|
|
"""Register discovered skills in a fresh ToolRegistry."""
|
|
registry = ToolRegistry()
|
|
discovered = [
|
|
DiscoveredAgentSkill(
|
|
name=f"tool-{i}",
|
|
description=f"Tool {i}",
|
|
path=f"/tmp/skills/tool-{i}",
|
|
)
|
|
for i in range(int(count))
|
|
]
|
|
registered, conflicts = register_discovered_skills(
|
|
discovered, registry, on_conflict="skip"
|
|
)
|
|
return {
|
|
"registered_count": len(registered),
|
|
"conflict_count": len(conflicts),
|
|
}
|
|
|
|
|
|
def register_agent_skills_with_collision(
|
|
strategy: str,
|
|
) -> dict[str, int]:
|
|
"""Register a skill that collides with an existing tool."""
|
|
registry = ToolRegistry()
|
|
existing = ToolSpec(
|
|
name="agent_skills/collider",
|
|
description="Existing tool",
|
|
handler=_noop_handler,
|
|
)
|
|
registry.register(existing)
|
|
|
|
discovered = [
|
|
DiscoveredAgentSkill(
|
|
name="collider",
|
|
description="Conflicting skill",
|
|
path="/tmp/skills/collider",
|
|
)
|
|
]
|
|
|
|
try:
|
|
registered, conflicts = register_discovered_skills(
|
|
discovered, registry, on_conflict=strategy
|
|
)
|
|
except ValueError:
|
|
return {"registered_count": 0, "conflict_count": 1}
|
|
|
|
return {
|
|
"registered_count": len(registered),
|
|
"conflict_count": len(conflicts),
|
|
}
|
|
|
|
|
|
def get_config_entry(key: str) -> Any:
|
|
"""Get a config entry from the registry."""
|
|
return ConfigService.get_entry(key)
|
|
|
|
|
|
def cleanup_temp_directory(path: str) -> None:
|
|
"""Remove a temporary directory."""
|
|
shutil.rmtree(path, ignore_errors=True)
|