1
0

feat: swarm E2E, MCP tools, timmy-serve L402, tests, notifications

Major Features:
- Auto-spawn persona agents (Echo, Forge, Seer) on app startup
- WebSocket broadcasts for real-time swarm UI updates
- MCP tool integration: web search, file I/O, shell, Python execution
- New /tools dashboard page showing agent capabilities
- Real timmy-serve start with L402 payment gating middleware
- Browser push notifications for briefings and task events

Tests:
- test_docker_agent.py: 9 tests for Docker agent runner
- test_swarm_integration_full.py: 18 E2E lifecycle tests
- Fixed all pytest warnings (436 tests, 0 warnings)

Improvements:
- Fixed coroutine warnings in coordinator broadcasts
- Fixed ResourceWarning for unclosed process pipes
- Added pytest-asyncio config to pyproject.toml
- Test isolation with proper event loop cleanup
This commit is contained in:
Alexander Payne
2026-02-22 19:01:04 -05:00
parent c5f86b8960
commit f0aa43533f
17 changed files with 1628 additions and 13 deletions

View File

@@ -0,0 +1,92 @@
"""Tools dashboard route — /tools endpoints.
Provides a dashboard page showing available tools, which agents have access
to which tools, and usage statistics.
"""
from pathlib import Path
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from swarm import registry as swarm_registry
from swarm.personas import PERSONAS
from timmy.tools import get_all_available_tools, get_tool_stats
router = APIRouter(tags=["tools"])
templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates"))
@router.get("/tools", response_class=HTMLResponse)
async def tools_page(request: Request):
"""Render the tools dashboard page."""
# Get all available tools
available_tools = get_all_available_tools()
# Get registered agents and their personas
agents = swarm_registry.list_agents()
agent_tools = []
for agent in agents:
# Determine which tools this agent has based on its capabilities/persona
tools_for_agent = []
# Check if it's a persona by name
persona_id = None
for pid, pdata in PERSONAS.items():
if pdata["name"].lower() == agent.name.lower():
persona_id = pid
break
if persona_id:
# Get tools for this persona
for tool_id, tool_info in available_tools.items():
if persona_id in tool_info["available_in"]:
tools_for_agent.append({
"id": tool_id,
"name": tool_info["name"],
"description": tool_info["description"],
})
elif agent.name.lower() == "timmy":
# Timmy has all tools
for tool_id, tool_info in available_tools.items():
tools_for_agent.append({
"id": tool_id,
"name": tool_info["name"],
"description": tool_info["description"],
})
# Get tool stats for this agent
stats = get_tool_stats(agent.id)
agent_tools.append({
"id": agent.id,
"name": agent.name,
"status": agent.status,
"tools": tools_for_agent,
"stats": stats,
})
# Calculate overall stats
total_calls = sum(a["stats"]["total_calls"] for a in agent_tools if a["stats"])
return templates.TemplateResponse(
request,
"tools.html",
{
"page_title": "Tools & Capabilities",
"available_tools": available_tools,
"agent_tools": agent_tools,
"total_calls": total_calls,
},
)
@router.get("/tools/api/stats")
async def tools_api_stats():
"""Return tool usage statistics as JSON."""
return {
"all_stats": get_tool_stats(),
"available_tools": list(get_all_available_tools().keys()),
}