This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/tests/timmy/test_tool_safety.py
Alexander Whitestone 904a7c564e feat: migrate to Agno native HITL tool confirmation flow (#158)
Replace the homebrew regex-based tool extraction and manual dispatch
(tool_executor.py) with Agno's built-in Human-In-The-Loop confirmation:

- Toolkit(requires_confirmation_tools=...) marks dangerous tools
- agent.run() returns RunOutput with status=paused when confirmation needed
- RunRequirement.confirm()/reject() + agent.continue_run() resumes execution

Dashboard and Discord vendor both use the native flow. DuckDuckGo import
isolated so its absence doesn't kill all tools. Test stubs cleaned up
(agno is a real dependency, only truly optional packages stubbed).

1384 tests pass in parallel (~14s).

Co-authored-by: Trip T <trip@local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:54:04 -04:00

116 lines
3.9 KiB
Python

"""Tests for timmy.tool_safety — classification, extraction, and formatting."""
import pytest
from timmy.tool_safety import (
extract_tool_calls,
format_action_description,
get_impact_level,
requires_confirmation,
)
# ---------------------------------------------------------------------------
# requires_confirmation
# ---------------------------------------------------------------------------
class TestRequiresConfirmation:
def test_dangerous_tools(self):
for tool in ("shell", "python", "write_file", "aider", "plan_and_execute"):
assert requires_confirmation(tool) is True
def test_safe_tools(self):
for tool in ("web_search", "calculator", "read_file", "list_files"):
assert requires_confirmation(tool) is False
def test_unknown_defaults_to_dangerous(self):
assert requires_confirmation("totally_unknown") is True
# ---------------------------------------------------------------------------
# extract_tool_calls
# ---------------------------------------------------------------------------
class TestExtractToolCalls:
def test_arguments_format(self):
text = (
'Creating dir. {"name": "shell", "arguments": {"args": ["mkdir", "-p", "/tmp/test"]}}'
)
calls = extract_tool_calls(text)
assert len(calls) == 1
assert calls[0][0] == "shell"
assert calls[0][1]["args"] == ["mkdir", "-p", "/tmp/test"]
def test_parameters_format(self):
text = 'Result: {"name": "python", "parameters": {"code": "print(1+1)"}}'
calls = extract_tool_calls(text)
assert len(calls) == 1
assert calls[0][0] == "python"
def test_multiple_calls(self):
text = (
'Step 1: {"name": "shell", "arguments": {"args": ["mkdir", "/tmp/a"]}} '
'Step 2: {"name": "write_file", "arguments": {"file_name": "/tmp/a/f.md", "contents": "hi"}}'
)
calls = extract_tool_calls(text)
assert len(calls) == 2
def test_empty_and_none(self):
assert extract_tool_calls("") == []
assert extract_tool_calls(None) == []
assert extract_tool_calls("Just normal text.") == []
def test_malformed_json(self):
text = '{"name": "shell", "arguments": {not valid json}}'
assert extract_tool_calls(text) == []
# ---------------------------------------------------------------------------
# format_action_description
# ---------------------------------------------------------------------------
class TestFormatActionDescription:
def test_shell_command(self):
desc = format_action_description("shell", {"command": "ls -la /tmp"})
assert "ls -la /tmp" in desc
def test_shell_args_list(self):
desc = format_action_description("shell", {"args": ["mkdir", "-p", "/tmp/t"]})
assert "mkdir -p /tmp/t" in desc
def test_write_file(self):
desc = format_action_description(
"write_file", {"file_name": "/tmp/f.md", "contents": "hello world"}
)
assert "/tmp/f.md" in desc
assert "11 chars" in desc
def test_python(self):
desc = format_action_description("python", {"code": "print(42)"})
assert "print(42)" in desc
def test_unknown_tool(self):
desc = format_action_description("custom_tool", {"key": "value"})
assert "custom_tool" in desc
# ---------------------------------------------------------------------------
# get_impact_level
# ---------------------------------------------------------------------------
class TestGetImpactLevel:
def test_high(self):
assert get_impact_level("shell") == "high"
assert get_impact_level("python") == "high"
def test_medium(self):
assert get_impact_level("write_file") == "medium"
assert get_impact_level("aider") == "medium"
def test_low(self):
assert get_impact_level("web_search") == "low"
assert get_impact_level("unknown") == "low"