forked from Rockachopa/Timmy-time-dashboard
feat: replace custom Gitea client with MCP servers
Replace the bespoke GiteaHand httpx client and tools_gitea.py wrappers with official MCP tool servers (gitea-mcp + filesystem MCP), wired into Agno via MCPTools. Switch all session functions to async (arun/acontinue_run) so MCP tools auto-connect. Delete ~1070 lines of custom Gitea code. - Create src/timmy/mcp_tools.py with MCP factories + standalone issue bridge - Wire MCPTools into agent.py tool list (Gitea + filesystem) - Switch session.py chat/chat_with_tools/continue_chat to async - Update all callers (dashboard routes, Discord vendor, CLI, thinking engine) - Add gitea_token fallback from ~/.config/gitea/token - Add MCP session cleanup to app shutdown hook - Update tool_safety.py for MCP tool names - 11 new tests, all 1417 passing, coverage 74.2% Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""Tests for dashboard tool confirmation flow using native Agno RunOutput."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
|
||||
def _mock_completed_run(content="Just a reply."):
|
||||
@@ -47,7 +47,7 @@ def test_chat_with_tool_call_shows_approval_card(client):
|
||||
item = _mock_approval_item()
|
||||
|
||||
with (
|
||||
patch("dashboard.routes.agents.chat_with_tools", return_value=run),
|
||||
patch("dashboard.routes.agents.chat_with_tools", new_callable=AsyncMock, return_value=run),
|
||||
patch("timmy.approvals.create_item", return_value=item),
|
||||
):
|
||||
response = client.post("/agents/default/chat", data={"message": "run echo hello"})
|
||||
@@ -76,7 +76,7 @@ def test_chat_tool_card_contains_impact_badge(client):
|
||||
item = _mock_approval_item()
|
||||
|
||||
with (
|
||||
patch("dashboard.routes.agents.chat_with_tools", return_value=run),
|
||||
patch("dashboard.routes.agents.chat_with_tools", new_callable=AsyncMock, return_value=run),
|
||||
patch("timmy.approvals.create_item", return_value=item),
|
||||
):
|
||||
response = client.post("/agents/default/chat", data={"message": "run it"})
|
||||
@@ -90,7 +90,7 @@ def test_chat_tool_card_has_htmx_approve_endpoint(client):
|
||||
item = _mock_approval_item()
|
||||
|
||||
with (
|
||||
patch("dashboard.routes.agents.chat_with_tools", return_value=run),
|
||||
patch("dashboard.routes.agents.chat_with_tools", new_callable=AsyncMock, return_value=run),
|
||||
patch("timmy.approvals.create_item", return_value=item),
|
||||
):
|
||||
response = client.post("/agents/default/chat", data={"message": "run it"})
|
||||
@@ -109,7 +109,7 @@ def _create_pending_tool(client, approval_id="test-approval-123"):
|
||||
item = _mock_approval_item(approval_id)
|
||||
|
||||
with (
|
||||
patch("dashboard.routes.agents.chat_with_tools", return_value=run),
|
||||
patch("dashboard.routes.agents.chat_with_tools", new_callable=AsyncMock, return_value=run),
|
||||
patch("timmy.approvals.create_item", return_value=item),
|
||||
):
|
||||
response = client.post("/agents/default/chat", data={"message": "run it"})
|
||||
@@ -131,7 +131,9 @@ def test_approve_executes_tool_and_returns_result(client):
|
||||
result_run.content = "Done."
|
||||
|
||||
with (
|
||||
patch("dashboard.routes.agents.continue_chat", return_value=result_run),
|
||||
patch(
|
||||
"dashboard.routes.agents.continue_chat", new_callable=AsyncMock, return_value=result_run
|
||||
),
|
||||
patch("timmy.approvals.approve"),
|
||||
):
|
||||
response = client.post(f"/agents/default/tool/{approval_id}/approve")
|
||||
@@ -153,7 +155,9 @@ def test_approve_same_id_twice_returns_404(client):
|
||||
result_run = _mock_completed_run("ok")
|
||||
|
||||
with (
|
||||
patch("dashboard.routes.agents.continue_chat", return_value=result_run),
|
||||
patch(
|
||||
"dashboard.routes.agents.continue_chat", new_callable=AsyncMock, return_value=result_run
|
||||
),
|
||||
patch("timmy.approvals.approve"),
|
||||
):
|
||||
client.post(f"/agents/default/tool/{approval_id}/approve")
|
||||
@@ -171,7 +175,11 @@ def test_reject_returns_rejected_card(client):
|
||||
approval_id = _create_pending_tool(client)
|
||||
|
||||
with (
|
||||
patch("dashboard.routes.agents.continue_chat", return_value=_mock_completed_run()),
|
||||
patch(
|
||||
"dashboard.routes.agents.continue_chat",
|
||||
new_callable=AsyncMock,
|
||||
return_value=_mock_completed_run(),
|
||||
),
|
||||
patch("timmy.approvals.reject"),
|
||||
):
|
||||
response = client.post(f"/agents/default/tool/{approval_id}/reject")
|
||||
|
||||
@@ -104,7 +104,9 @@ def _mock_run(content="Operational and ready."):
|
||||
|
||||
|
||||
def test_chat_agent_success(client):
|
||||
with patch("dashboard.routes.agents.chat_with_tools", return_value=_mock_run()):
|
||||
with patch(
|
||||
"dashboard.routes.agents.chat_with_tools", new_callable=AsyncMock, return_value=_mock_run()
|
||||
):
|
||||
response = client.post("/agents/default/chat", data={"message": "status?"})
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -113,7 +115,11 @@ def test_chat_agent_success(client):
|
||||
|
||||
|
||||
def test_chat_agent_shows_user_message(client):
|
||||
with patch("dashboard.routes.agents.chat_with_tools", return_value=_mock_run("Acknowledged.")):
|
||||
with patch(
|
||||
"dashboard.routes.agents.chat_with_tools",
|
||||
new_callable=AsyncMock,
|
||||
return_value=_mock_run("Acknowledged."),
|
||||
):
|
||||
response = client.post("/agents/default/chat", data={"message": "hello there"})
|
||||
|
||||
assert "hello there" in response.text
|
||||
@@ -123,6 +129,7 @@ def test_chat_agent_ollama_offline(client):
|
||||
# When Ollama is unreachable, chat shows the user message + error.
|
||||
with patch(
|
||||
"dashboard.routes.agents.chat_with_tools",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=Exception("Ollama unreachable"),
|
||||
):
|
||||
response = client.post("/agents/default/chat", data={"message": "ping"})
|
||||
@@ -147,7 +154,9 @@ def test_history_empty_shows_init_message(client):
|
||||
|
||||
def test_history_records_user_and_agent_messages(client):
|
||||
with patch(
|
||||
"dashboard.routes.agents.chat_with_tools", return_value=_mock_run("I am operational.")
|
||||
"dashboard.routes.agents.chat_with_tools",
|
||||
new_callable=AsyncMock,
|
||||
return_value=_mock_run("I am operational."),
|
||||
):
|
||||
client.post("/agents/default/chat", data={"message": "status check"})
|
||||
|
||||
@@ -158,6 +167,7 @@ def test_history_records_user_and_agent_messages(client):
|
||||
def test_history_records_error_when_offline(client):
|
||||
with patch(
|
||||
"dashboard.routes.agents.chat_with_tools",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=Exception("Ollama unreachable"),
|
||||
):
|
||||
client.post("/agents/default/chat", data={"message": "ping"})
|
||||
@@ -167,7 +177,11 @@ def test_history_records_error_when_offline(client):
|
||||
|
||||
|
||||
def test_history_clear_resets_to_init_message(client):
|
||||
with patch("dashboard.routes.agents.chat_with_tools", return_value=_mock_run("Acknowledged.")):
|
||||
with patch(
|
||||
"dashboard.routes.agents.chat_with_tools",
|
||||
new_callable=AsyncMock,
|
||||
return_value=_mock_run("Acknowledged."),
|
||||
):
|
||||
client.post("/agents/default/chat", data={"message": "hello"})
|
||||
|
||||
response = client.delete("/agents/default/history")
|
||||
@@ -176,7 +190,11 @@ def test_history_clear_resets_to_init_message(client):
|
||||
|
||||
|
||||
def test_history_empty_after_clear(client):
|
||||
with patch("dashboard.routes.agents.chat_with_tools", return_value=_mock_run("OK.")):
|
||||
with patch(
|
||||
"dashboard.routes.agents.chat_with_tools",
|
||||
new_callable=AsyncMock,
|
||||
return_value=_mock_run("OK."),
|
||||
):
|
||||
client.post("/agents/default/chat", data={"message": "test"})
|
||||
|
||||
client.delete("/agents/default/history")
|
||||
|
||||
Reference in New Issue
Block a user