183 lines
5.4 KiB
Python
183 lines
5.4 KiB
Python
"""Robot Framework helper for inline tool executor smoke tests.
|
|
|
|
Provides a CLI-style interface for Robot to invoke inline tool
|
|
execution, validation, and write-guard checks.
|
|
Exit code 0 = success, 1 = failure.
|
|
|
|
Usage:
|
|
python robot/helper_skill_inline.py execute-simple
|
|
python robot/helper_skill_inline.py execute-timeout
|
|
python robot/helper_skill_inline.py execute-truncate
|
|
python robot/helper_skill_inline.py validate-no-code
|
|
python robot/helper_skill_inline.py write-guard
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# 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
|
|
SkillInlineTool,
|
|
)
|
|
from cleveragents.domain.models.core.tool import ( # noqa: E402
|
|
ToolCapability,
|
|
ToolSource,
|
|
)
|
|
from cleveragents.skills.context import SkillContext # noqa: E402
|
|
from cleveragents.skills.inline_executor import InlineToolExecutor # noqa: E402
|
|
|
|
|
|
def _cmd_execute_simple() -> int:
|
|
"""Test simple inline tool execution."""
|
|
tool = SkillInlineTool(
|
|
description="Simple print",
|
|
source=ToolSource.CUSTOM,
|
|
code='print("hello-robot")',
|
|
)
|
|
ctx = SkillContext(
|
|
plan_id="plan-robot-inline",
|
|
project_id="proj-robot-inline",
|
|
sandbox_path=Path("/tmp/robot-inline-sandbox"),
|
|
)
|
|
executor = InlineToolExecutor()
|
|
result = executor.execute(tool, ctx, {})
|
|
if not result.success:
|
|
print(f"inline-fail: {result.error_message}")
|
|
return 1
|
|
if "hello-robot" not in str(result.output):
|
|
print(f"inline-fail: unexpected output {result.output}")
|
|
return 1
|
|
print("inline-execute-ok")
|
|
return 0
|
|
|
|
|
|
def _cmd_execute_timeout() -> int:
|
|
"""Test inline tool timeout."""
|
|
tool = SkillInlineTool(
|
|
description="Sleeping tool",
|
|
source=ToolSource.CUSTOM,
|
|
code="import time; time.sleep(5)",
|
|
)
|
|
ctx = SkillContext(
|
|
plan_id="plan-robot-timeout",
|
|
project_id="proj-robot-timeout",
|
|
sandbox_path=Path("/tmp/robot-timeout-sandbox"),
|
|
)
|
|
executor = InlineToolExecutor(max_runtime_seconds=0.1)
|
|
result = executor.execute(tool, ctx, {})
|
|
if result.success:
|
|
print("inline-fail: expected timeout failure")
|
|
return 1
|
|
if "timed out" not in (result.error_message or ""):
|
|
print(f"inline-fail: unexpected error: {result.error_message}")
|
|
return 1
|
|
print("inline-timeout-ok")
|
|
return 0
|
|
|
|
|
|
def _cmd_execute_truncate() -> int:
|
|
"""Test inline tool output truncation."""
|
|
tool = SkillInlineTool(
|
|
description="Large output tool",
|
|
source=ToolSource.CUSTOM,
|
|
code='print("X" * 2000)',
|
|
)
|
|
ctx = SkillContext(
|
|
plan_id="plan-robot-truncate",
|
|
project_id="proj-robot-truncate",
|
|
sandbox_path=Path("/tmp/robot-truncate-sandbox"),
|
|
)
|
|
executor = InlineToolExecutor(max_output_bytes=100)
|
|
result = executor.execute(tool, ctx, {})
|
|
if not result.success:
|
|
print(f"inline-fail: {result.error_message}")
|
|
return 1
|
|
if not result.truncated:
|
|
print("inline-fail: expected truncated=True")
|
|
return 1
|
|
print("inline-truncate-ok")
|
|
return 0
|
|
|
|
|
|
def _cmd_validate_no_code() -> int:
|
|
"""Test validation of tool without code."""
|
|
tool = SkillInlineTool(
|
|
description="No-code tool",
|
|
source=ToolSource.CUSTOM,
|
|
code=None,
|
|
)
|
|
executor = InlineToolExecutor()
|
|
errors = executor.validate_tool(tool)
|
|
if not errors:
|
|
print("inline-fail: expected validation errors")
|
|
return 1
|
|
if not any("non-empty code" in e for e in errors):
|
|
print(f"inline-fail: unexpected errors: {errors}")
|
|
return 1
|
|
print("inline-validate-ok")
|
|
return 0
|
|
|
|
|
|
def _cmd_write_guard() -> int:
|
|
"""Test write guard blocks execution in read-only context."""
|
|
tool = SkillInlineTool(
|
|
description="Write tool",
|
|
source=ToolSource.CUSTOM,
|
|
code='print("writing")',
|
|
capability=ToolCapability(writes=True),
|
|
)
|
|
ctx = SkillContext(
|
|
plan_id="plan-robot-guard",
|
|
project_id="proj-robot-guard",
|
|
sandbox_path=Path("/tmp/robot-guard-sandbox"),
|
|
read_only=True,
|
|
)
|
|
executor = InlineToolExecutor()
|
|
result = executor.execute(tool, ctx, {})
|
|
if result.success:
|
|
print("inline-fail: expected write guard to block")
|
|
return 1
|
|
if "denied" not in (result.error_message or "").lower():
|
|
print(f"inline-fail: unexpected error: {result.error_message}")
|
|
return 1
|
|
print("inline-guard-ok")
|
|
return 0
|
|
|
|
|
|
_COMMANDS = {
|
|
"execute-simple": _cmd_execute_simple,
|
|
"execute-timeout": _cmd_execute_timeout,
|
|
"execute-truncate": _cmd_execute_truncate,
|
|
"validate-no-code": _cmd_validate_no_code,
|
|
"write-guard": _cmd_write_guard,
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
"""Entry point called by Robot Framework ``Run Process``."""
|
|
if len(sys.argv) < 2:
|
|
print(
|
|
"Usage: helper_skill_inline.py "
|
|
"<execute-simple|execute-timeout|execute-truncate"
|
|
"|validate-no-code|write-guard>"
|
|
)
|
|
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())
|