198 lines
6.1 KiB
Python
198 lines
6.1 KiB
Python
"""Robot Framework helper for skill context and registry smoke tests.
|
|
|
|
Provides a CLI-style interface for Robot to invoke context creation,
|
|
resource resolution, change tracking, write guard, and registry operations.
|
|
Exit code 0 = success, 1 = failure.
|
|
|
|
Usage:
|
|
python robot/helper_skill_context.py create-context
|
|
python robot/helper_skill_context.py resolve-resource
|
|
python robot/helper_skill_context.py track-invocation
|
|
python robot/helper_skill_context.py write-guard
|
|
python robot/helper_skill_context.py registry-crud
|
|
python robot/helper_skill_context.py registry-list
|
|
python robot/helper_skill_context.py registry-resolve
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
# Ensure the src directory is on the import path.
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
from cleveragents.domain.models.core.skill import ( # noqa: E402
|
|
Skill,
|
|
SkillResolver,
|
|
)
|
|
from cleveragents.skills.context import SkillContext, SkillExecutionError # noqa: E402
|
|
from cleveragents.skills.protocol import ( # noqa: E402
|
|
SkillDefinition,
|
|
SkillMetadata,
|
|
)
|
|
from cleveragents.skills.registry import SkillRegistry # noqa: E402
|
|
|
|
|
|
def _make_defn(name: str, **kw: Any) -> SkillDefinition:
|
|
"""Create a SkillDefinition with defaults."""
|
|
skill = Skill(name=name, description=f"Test {name}", **kw)
|
|
resolver = SkillResolver()
|
|
resolved = resolver.resolve_tools(skill, {})
|
|
meta = SkillMetadata.from_skill(skill, resolved=resolved)
|
|
return SkillDefinition(skill=skill, resolved_tools=resolved, metadata=meta)
|
|
|
|
|
|
def _cmd_create_context() -> int:
|
|
"""Test SkillContext creation."""
|
|
ctx = SkillContext(
|
|
plan_id="plan-robot",
|
|
project_id="proj-robot",
|
|
sandbox_path=Path("/tmp/robot-sandbox"),
|
|
)
|
|
if ctx.plan_id != "plan-robot":
|
|
print(f"skill-context-fail: unexpected plan_id {ctx.plan_id}")
|
|
return 1
|
|
if ctx.is_read_only():
|
|
print("skill-context-fail: should not be read-only")
|
|
return 1
|
|
print(f"skill-context-ok: plan={ctx.plan_id}")
|
|
return 0
|
|
|
|
|
|
def _cmd_resolve_resource() -> int:
|
|
"""Test resource resolution."""
|
|
ctx = SkillContext(
|
|
plan_id="plan-res",
|
|
project_id="proj-res",
|
|
sandbox_path=Path("/tmp/res-sandbox"),
|
|
resource_bindings={"git-checkout": "/repo/path"},
|
|
)
|
|
result = ctx.resolve_resource("git-checkout")
|
|
if result != "/repo/path":
|
|
print(f"skill-context-fail: unexpected resource {result}")
|
|
return 1
|
|
print(f"skill-context-resolve-ok: {result}")
|
|
return 0
|
|
|
|
|
|
def _cmd_track_invocation() -> int:
|
|
"""Test change tracking."""
|
|
ctx = SkillContext(
|
|
plan_id="plan-track",
|
|
project_id="proj-track",
|
|
sandbox_path=Path("/tmp/track-sandbox"),
|
|
)
|
|
ctx.register_tool_invocation("local/test-tool", {"a": 1}, {"b": 2}, 15.5)
|
|
if len(ctx.change_tracker) != 1:
|
|
print(f"skill-context-fail: expected 1 record, got {len(ctx.change_tracker)}")
|
|
return 1
|
|
if ctx.change_tracker[0]["tool_name"] != "local/test-tool":
|
|
print("skill-context-fail: wrong tool_name in tracker")
|
|
return 1
|
|
print("skill-context-track-ok")
|
|
return 0
|
|
|
|
|
|
def _cmd_write_guard() -> int:
|
|
"""Test write guard enforcement."""
|
|
ctx = SkillContext(
|
|
plan_id="plan-guard",
|
|
project_id="proj-guard",
|
|
sandbox_path=Path("/tmp/guard-sandbox"),
|
|
read_only=True,
|
|
)
|
|
try:
|
|
ctx.enforce_write_guard("local/write-tool")
|
|
print("skill-context-fail: expected SkillError")
|
|
return 1
|
|
except SkillExecutionError as e:
|
|
if e.error_type.value != "permission_denied":
|
|
print(f"skill-context-fail: unexpected error type {e.error_type}")
|
|
return 1
|
|
print("skill-context-guard-ok")
|
|
return 0
|
|
|
|
|
|
def _cmd_registry_crud() -> int:
|
|
"""Test registry register/get/unregister."""
|
|
reg = SkillRegistry()
|
|
defn = _make_defn("local/robot-reg")
|
|
reg.register(defn)
|
|
got = reg.get("local/robot-reg")
|
|
if got.skill.name != "local/robot-reg":
|
|
print(f"skill-registry-fail: unexpected name {got.skill.name}")
|
|
return 1
|
|
reg.unregister("local/robot-reg")
|
|
try:
|
|
reg.get("local/robot-reg")
|
|
print("skill-registry-fail: expected not-found error")
|
|
return 1
|
|
except SkillExecutionError:
|
|
pass
|
|
print("skill-registry-crud-ok")
|
|
return 0
|
|
|
|
|
|
def _cmd_registry_list() -> int:
|
|
"""Test registry list_all."""
|
|
reg = SkillRegistry()
|
|
reg.register(_make_defn("local/list-a"))
|
|
reg.register(_make_defn("local/list-b"))
|
|
items = reg.list_all()
|
|
if len(items) != 2:
|
|
print(f"skill-registry-fail: expected 2, got {len(items)}")
|
|
return 1
|
|
print(f"skill-registry-list-ok: {len(items)} skills")
|
|
return 0
|
|
|
|
|
|
def _cmd_registry_resolve() -> int:
|
|
"""Test registry tool resolution."""
|
|
reg = SkillRegistry()
|
|
defn = _make_defn("local/resolve-skill", tool_refs=["local/tool-x"])
|
|
reg.register(defn)
|
|
tools = reg.resolve_tools("local/resolve-skill")
|
|
if len(tools) != 1:
|
|
print(f"skill-registry-fail: expected 1 tool, got {len(tools)}")
|
|
return 1
|
|
print(f"skill-registry-resolve-ok: {len(tools)} tools")
|
|
return 0
|
|
|
|
|
|
_COMMANDS = {
|
|
"create-context": _cmd_create_context,
|
|
"resolve-resource": _cmd_resolve_resource,
|
|
"track-invocation": _cmd_track_invocation,
|
|
"write-guard": _cmd_write_guard,
|
|
"registry-crud": _cmd_registry_crud,
|
|
"registry-list": _cmd_registry_list,
|
|
"registry-resolve": _cmd_registry_resolve,
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
"""Entry point called by Robot Framework ``Run Process``."""
|
|
if len(sys.argv) < 2:
|
|
print(
|
|
"Usage: helper_skill_context.py "
|
|
"<create-context|resolve-resource|track-invocation|write-guard"
|
|
"|registry-crud|registry-list|registry-resolve>"
|
|
)
|
|
return 1
|
|
|
|
command = sys.argv[1]
|
|
handler = _COMMANDS.get(command)
|
|
if handler is None:
|
|
print(f"Unknown command: {command}")
|
|
return 1
|
|
|
|
return handler()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|