Files
Agentic-OS/backend/app/agent/tool_runner.py
nearxos 6185b9b85a Initial commit: Agentic OS troubleshooting platform
Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend +
LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini ->
DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials,
RBAC, audit log, project-memory + Obsidian integrations, and editable
troubleshooting decision rules tuned for the GeneseasX vessel stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 22:11:07 +03:00

166 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Run MCP tools with live events (start → done) and auto-reload on failure."""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from app.agent.mcp_helpers import tool_result_ok
from app.agent.tool_events import tool_event_payload
from app.mcp_manager.recovery import call_tool_resilient
from app.services.events import get_redis
from app.services.task_runtime import ensure_not_cancelled
EmitFn = Callable[[int, str, str, dict | None], Awaitable[None]]
def _repair_key(task_id: int, server: str, tool: str) -> str:
return f"agentic:task:{task_id}:mcp_repair:{server}:{tool}"
async def _repair_attempted(task_id: int, server: str, tool: str) -> bool:
return bool(await get_redis().get(_repair_key(task_id, server, tool)))
async def _mark_repair_attempted(task_id: int, server: str, tool: str) -> None:
await get_redis().set(_repair_key(task_id, server, tool), "1", ex=86400)
async def invoke_tool(
task_id: int,
device: str,
server: str,
tool: str,
args: dict,
manager,
emit: EmitFn,
*,
task_title: str = "",
task_issue: str = "",
) -> tuple[dict, bool]:
await ensure_not_cancelled(task_id)
await emit(
task_id,
"tool_start",
f"{device} {tool}",
tool_event_payload(device=device, tool=tool, args=args, status="running"),
)
try:
res = await call_tool_resilient(
manager, server, tool, args, task_id=task_id, emit=emit
)
ok = tool_result_ok(res)
if not ok:
retry = await _maybe_mcp_repair(
task_id,
device,
server,
tool,
args,
res.get("text"),
manager,
emit,
task_title=task_title,
task_issue=task_issue,
)
if retry is not None:
return retry
await emit(
task_id,
"tool_call",
f"{device} {tool}",
tool_event_payload(
device=device,
tool=tool,
args=args,
status="done",
ok=ok,
output=res.get("text", ""),
),
)
return res, ok
except Exception as exc:
retry = await _maybe_mcp_repair(
task_id,
device,
server,
tool,
args,
str(exc),
manager,
emit,
task_title=task_title,
task_issue=task_issue,
error=str(exc),
)
if retry is not None:
return retry
await emit(
task_id,
"tool_call",
f"{device} {tool} failed",
tool_event_payload(
device=device,
tool=tool,
args=args,
status="error",
ok=False,
error=str(exc),
),
)
return {"text": str(exc), "isError": True}, False
async def _maybe_mcp_repair(
task_id: int,
device: str,
server: str,
tool: str,
args: dict,
output: str | None,
manager,
emit: EmitFn,
*,
task_title: str,
task_issue: str,
error: str | None = None,
) -> tuple[dict, bool] | None:
if await _repair_attempted(task_id, server, tool):
return None
from app.agent.mcp_development import maybe_repair_after_tool_failure
await _mark_repair_attempted(task_id, server, tool)
return await maybe_repair_after_tool_failure(
task_id=task_id,
device=device,
server=server,
tool=tool,
args=args,
issue=task_issue or task_title,
error=error,
output=output,
manager=manager,
emit=emit,
task_title=task_title,
)
async def invoke_connect(
task_id: int,
device: str,
server: str,
tool: str,
args: dict,
manager,
emit: EmitFn,
) -> bool:
try:
res = await call_tool_resilient(
manager, server, tool, args, task_id=task_id, emit=emit
)
ok = tool_result_ok(res)
msg = f"{device}: {tool} ok" if ok else f"{device}: {tool} failed — {res.get('text', '')[:200]}"
await emit(task_id, "connect", msg, {"output": res.get("text", "")[:400], "ok": ok, "tool": tool})
return ok
except Exception as exc:
await emit(task_id, "connect", f"{device}: {tool} failed — {exc}", {"ok": False, "tool": tool})
return False