Timmy can now delegate coding tasks to Kimi CLI (262K context). Includes timeout handling, workdir validation, output truncation. Sovereign division of labor — Timmy plans, Kimi codes.
153 lines
5.7 KiB
Python
153 lines
5.7 KiB
Python
"""Tests for timmy.tools_delegation — delegate_task and list_swarm_agents.
|
|
|
|
Agent IDs are now defined in config/agents.yaml, not hardcoded Python.
|
|
Tests reference the YAML-defined IDs: orchestrator, researcher, coder,
|
|
writer, memory, experimenter.
|
|
"""
|
|
|
|
from timmy.tools_delegation import delegate_task, list_swarm_agents
|
|
|
|
|
|
class TestDelegateTask:
|
|
def test_unknown_agent_returns_error(self):
|
|
result = delegate_task("nonexistent", "do something")
|
|
assert result["success"] is False
|
|
assert "Unknown agent" in result["error"]
|
|
assert result["task_id"] is None
|
|
|
|
def test_valid_agent_names_normalised(self):
|
|
# Agent IDs are lowercased; whitespace should be stripped
|
|
result = delegate_task(" Researcher ", "think about it")
|
|
assert "Unknown agent" not in result.get("error", "")
|
|
|
|
def test_invalid_priority_defaults_to_normal(self):
|
|
# Even with bad priority, delegate_task should not crash
|
|
result = delegate_task("coder", "build", priority="ultra")
|
|
assert isinstance(result, dict)
|
|
|
|
def test_all_valid_agents_accepted(self):
|
|
# These IDs match config/agents.yaml
|
|
valid_agents = ["orchestrator", "researcher", "coder", "writer", "memory", "experimenter"]
|
|
for agent in valid_agents:
|
|
result = delegate_task(agent, "test task")
|
|
assert "Unknown agent" not in result.get("error", ""), f"{agent} rejected"
|
|
|
|
def test_old_agent_names_no_longer_valid(self):
|
|
# Old hardcoded names should not work anymore
|
|
for old_name in ["seer", "forge", "echo", "helm", "quill", "mace"]:
|
|
result = delegate_task(old_name, "test")
|
|
assert result["success"] is False
|
|
assert "Unknown agent" in result["error"]
|
|
|
|
|
|
class TestListSwarmAgents:
|
|
def test_returns_agents_from_yaml(self):
|
|
result = list_swarm_agents()
|
|
assert result["success"] is True
|
|
assert len(result["agents"]) > 0
|
|
agent_names = [a["name"] for a in result["agents"]]
|
|
# These names come from config/agents.yaml
|
|
assert "Seer" in agent_names
|
|
assert "Forge" in agent_names
|
|
assert "Timmy" in agent_names
|
|
|
|
|
|
class TestDelegateToKimi:
|
|
"""Tests for delegate_to_kimi() — Timmy's Kimi delegation tool."""
|
|
|
|
def test_returns_dict(self, monkeypatch):
|
|
"""delegate_to_kimi should always return a dict."""
|
|
import shutil
|
|
|
|
monkeypatch.setattr(shutil, "which", lambda x: None)
|
|
from timmy.tools_delegation import delegate_to_kimi
|
|
|
|
result = delegate_to_kimi("test task")
|
|
assert isinstance(result, dict)
|
|
assert "success" in result
|
|
|
|
def test_kimi_not_found_returns_error(self, monkeypatch):
|
|
"""Should handle missing kimi CLI gracefully."""
|
|
import shutil
|
|
|
|
monkeypatch.setattr(shutil, "which", lambda x: None)
|
|
from timmy.tools_delegation import delegate_to_kimi
|
|
|
|
result = delegate_to_kimi("test task")
|
|
assert result["success"] is False
|
|
assert "not found" in result["error"]
|
|
|
|
def test_invalid_workdir_returns_error(self, monkeypatch):
|
|
"""Should reject non-existent working directories."""
|
|
import shutil
|
|
|
|
monkeypatch.setattr(shutil, "which", lambda x: "/usr/bin/kimi")
|
|
from timmy.tools_delegation import delegate_to_kimi
|
|
|
|
result = delegate_to_kimi("test", working_directory="/nonexistent/path")
|
|
assert result["success"] is False
|
|
assert "does not exist" in result["error"]
|
|
|
|
def test_timeout_returns_error(self, monkeypatch):
|
|
"""Should handle subprocess timeout gracefully."""
|
|
import shutil
|
|
import subprocess
|
|
|
|
monkeypatch.setattr(shutil, "which", lambda x: "/usr/bin/kimi")
|
|
|
|
def timeout_run(*args, **kwargs):
|
|
raise subprocess.TimeoutExpired(cmd="kimi", timeout=300)
|
|
|
|
monkeypatch.setattr(subprocess, "run", timeout_run)
|
|
from timmy.tools_delegation import delegate_to_kimi
|
|
|
|
result = delegate_to_kimi("complex task")
|
|
assert result["success"] is False
|
|
assert "timed out" in result["error"].lower()
|
|
|
|
def test_successful_delegation(self, monkeypatch):
|
|
"""Should capture Kimi's output on success."""
|
|
import shutil
|
|
import subprocess
|
|
|
|
monkeypatch.setattr(shutil, "which", lambda x: "/usr/bin/kimi")
|
|
|
|
def mock_run(*args, **kwargs):
|
|
return subprocess.CompletedProcess(
|
|
args=args[0] if args else [],
|
|
returncode=0,
|
|
stdout="Fixed the bug in session.py\n\nChanges:\n- Added null check",
|
|
stderr="",
|
|
)
|
|
|
|
monkeypatch.setattr(subprocess, "run", mock_run)
|
|
from config import settings
|
|
from timmy.tools_delegation import delegate_to_kimi
|
|
|
|
result = delegate_to_kimi("fix session bug", working_directory=settings.repo_root)
|
|
assert result["success"] is True
|
|
assert "Fixed the bug" in result["output"]
|
|
assert result["return_code"] == 0
|
|
|
|
def test_default_workdir_uses_repo_root(self, monkeypatch):
|
|
"""Empty working_directory should default to settings.repo_root."""
|
|
import shutil
|
|
import subprocess
|
|
|
|
calls = []
|
|
monkeypatch.setattr(shutil, "which", lambda x: "/usr/bin/kimi")
|
|
|
|
def capture_run(*args, **kwargs):
|
|
calls.append(kwargs.get("cwd", ""))
|
|
return subprocess.CompletedProcess(
|
|
args=args[0] if args else [], returncode=0, stdout="done", stderr=""
|
|
)
|
|
|
|
monkeypatch.setattr(subprocess, "run", capture_run)
|
|
from config import settings
|
|
from timmy.tools_delegation import delegate_to_kimi
|
|
|
|
delegate_to_kimi("test task")
|
|
assert len(calls) == 1
|
|
assert calls[0] == settings.repo_root
|