mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 14:07:55 +00:00
* fix(channels): wire channel→agent handler and fix Telegram send pipeline * format code * add supported tests
144 lines
4.3 KiB
Python
144 lines
4.3 KiB
Python
"""Tests for activity-based stall detection."""
|
|
|
|
import time
|
|
from unittest.mock import patch
|
|
|
|
from openjarvis.agents._stubs import AgentResult
|
|
from openjarvis.agents.executor import AgentExecutor
|
|
from openjarvis.agents.manager import AgentManager
|
|
from openjarvis.core.events import EventBus, EventType
|
|
|
|
|
|
def test_activity_tracking_updates_last_activity_at(tmp_path):
|
|
"""EventBus TOOL_CALL_START updates last_activity_at for the right agent."""
|
|
mgr = AgentManager(str(tmp_path / "test.db"))
|
|
bus = EventBus()
|
|
executor = AgentExecutor(mgr, bus)
|
|
|
|
agent = mgr.create_agent("stall-test")
|
|
|
|
def fake_invoke(agent_dict):
|
|
bus.publish(
|
|
EventType.TOOL_CALL_START,
|
|
{
|
|
"agent": agent_dict["id"],
|
|
"tool": "web_search",
|
|
},
|
|
)
|
|
return AgentResult(content="done", metadata={})
|
|
|
|
with patch.object(executor, "_invoke_agent", side_effect=fake_invoke):
|
|
executor.execute_tick(agent["id"])
|
|
|
|
updated = mgr.get_agent(agent["id"])
|
|
assert updated["last_activity_at"] is not None
|
|
assert updated["last_activity_at"] > 0
|
|
mgr.close()
|
|
|
|
|
|
def test_activity_tracking_filters_by_agent_id(tmp_path):
|
|
"""Events from other agents don't update this agent's last_activity_at."""
|
|
mgr = AgentManager(str(tmp_path / "test.db"))
|
|
bus = EventBus()
|
|
executor = AgentExecutor(mgr, bus)
|
|
|
|
agent_a = mgr.create_agent("agent-a")
|
|
agent_b = mgr.create_agent("agent-b")
|
|
|
|
def fake_invoke(agent_dict):
|
|
# Emit event for agent_b while agent_a is executing
|
|
bus.publish(
|
|
EventType.TOOL_CALL_START,
|
|
{
|
|
"agent": agent_b["id"],
|
|
"tool": "web_search",
|
|
},
|
|
)
|
|
return AgentResult(content="done", metadata={})
|
|
|
|
with patch.object(executor, "_invoke_agent", side_effect=fake_invoke):
|
|
executor.execute_tick(agent_a["id"])
|
|
|
|
updated_b = mgr.get_agent(agent_b["id"])
|
|
assert updated_b["last_activity_at"] is None
|
|
mgr.close()
|
|
|
|
|
|
def test_reconcile_detects_stalled_agent(tmp_path):
|
|
"""_reconcile() marks agent as stalled when last_activity_at is too old."""
|
|
mgr = AgentManager(str(tmp_path / "test.db"))
|
|
bus = EventBus(record_history=True)
|
|
executor = AgentExecutor(mgr, bus)
|
|
from openjarvis.agents.scheduler import AgentScheduler
|
|
|
|
scheduler = AgentScheduler(mgr, executor, event_bus=bus)
|
|
|
|
agent = mgr.create_agent(
|
|
"stall-me",
|
|
config={
|
|
"timeout_seconds": 10,
|
|
"max_stall_retries": 3,
|
|
},
|
|
)
|
|
mgr.update_agent(agent["id"], status="running", last_activity_at=time.time() - 30)
|
|
|
|
scheduler._reconcile()
|
|
|
|
updated = mgr.get_agent(agent["id"])
|
|
assert updated["stall_retries"] == 1
|
|
|
|
stall_events = [
|
|
e for e in bus.history if e.event_type == EventType.AGENT_STALL_DETECTED
|
|
]
|
|
assert len(stall_events) == 1
|
|
mgr.close()
|
|
|
|
|
|
def test_reconcile_skips_active_agent(tmp_path):
|
|
"""_reconcile() does NOT mark agent as stalled if activity is recent."""
|
|
mgr = AgentManager(str(tmp_path / "test.db"))
|
|
bus = EventBus(record_history=True)
|
|
executor = AgentExecutor(mgr, bus)
|
|
from openjarvis.agents.scheduler import AgentScheduler
|
|
|
|
scheduler = AgentScheduler(mgr, executor, event_bus=bus)
|
|
|
|
agent = mgr.create_agent("active", config={"timeout_seconds": 10})
|
|
mgr.update_agent(agent["id"], status="running", last_activity_at=time.time() - 2)
|
|
|
|
scheduler._reconcile()
|
|
|
|
updated = mgr.get_agent(agent["id"])
|
|
assert updated["status"] == "running"
|
|
mgr.close()
|
|
|
|
|
|
def test_reconcile_retries_exhausted_sets_error(tmp_path):
|
|
"""After max_stall_retries, agent goes to error status."""
|
|
mgr = AgentManager(str(tmp_path / "test.db"))
|
|
bus = EventBus()
|
|
executor = AgentExecutor(mgr, bus)
|
|
from openjarvis.agents.scheduler import AgentScheduler
|
|
|
|
scheduler = AgentScheduler(mgr, executor, event_bus=bus)
|
|
|
|
agent = mgr.create_agent(
|
|
"exhausted",
|
|
config={
|
|
"timeout_seconds": 10,
|
|
"max_stall_retries": 2,
|
|
},
|
|
)
|
|
mgr.update_agent(
|
|
agent["id"],
|
|
status="running",
|
|
last_activity_at=time.time() - 30,
|
|
stall_retries=2,
|
|
)
|
|
|
|
scheduler._reconcile()
|
|
|
|
updated = mgr.get_agent(agent["id"])
|
|
assert updated["status"] == "error"
|
|
mgr.close()
|