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>
This commit is contained in:
132
backend/app/agent/pfsense_profiles.py
Normal file
132
backend/app/agent/pfsense_profiles.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""pfSense diagnostics — issue-aware playbook selection."""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Awaitable, Callable
|
||||
|
||||
from app.agent.tool_runner import invoke_connect, invoke_tool
|
||||
|
||||
EmitFn = Callable[[int, str, str, dict | None], Awaitable[None]]
|
||||
|
||||
PFSENSE_MCP = "pfsense-mcp"
|
||||
|
||||
ArgBuilder = Callable[[dict], dict]
|
||||
|
||||
PFSENSE_DEFAULT_DIAGNOSTICS: list[tuple[str, ArgBuilder]] = [
|
||||
("pfsense_get_system_status", lambda _c: {}),
|
||||
("pfsense_list_interfaces", lambda _c: {}),
|
||||
("pfsense_list_gateways", lambda _c: {}),
|
||||
("pfsense_get_gateway_status", lambda _c: {}),
|
||||
]
|
||||
|
||||
PFSENSE_FIREWALL_DIAGNOSTICS: list[tuple[str, ArgBuilder]] = [
|
||||
("pfsense_list_interfaces", lambda _c: {}),
|
||||
("pfsense_list_firewall_rules", lambda _c: {"limit": 250}),
|
||||
("pfsense_list_port_forwards", lambda _c: {"limit": 100}),
|
||||
("pfsense_list_outbound_nat_mappings", lambda _c: {"limit": 100}),
|
||||
("pfsense_list_aliases", lambda _c: {"limit": 100}),
|
||||
]
|
||||
|
||||
_FIREWALL_TERMS = (
|
||||
"firewall",
|
||||
"rule",
|
||||
"rules",
|
||||
"policy",
|
||||
"policies",
|
||||
"filter rule",
|
||||
"nat",
|
||||
"port forward",
|
||||
"port-forward",
|
||||
"outbound nat",
|
||||
"inbound",
|
||||
"acl",
|
||||
)
|
||||
|
||||
|
||||
def _text_has_term(text: str, term: str) -> bool:
|
||||
term = term.lower().strip()
|
||||
if not term:
|
||||
return False
|
||||
if " " in term:
|
||||
return term in text
|
||||
return bool(re.search(rf"\b{re.escape(term)}\b", text))
|
||||
|
||||
|
||||
def wants_firewall_inventory(title: str, issue: str) -> bool:
|
||||
text = f"{title} {issue}".lower()
|
||||
return any(_text_has_term(text, term) for term in _FIREWALL_TERMS)
|
||||
|
||||
|
||||
def diagnostics_for_issue(title: str, issue: str) -> list[tuple[str, ArgBuilder]]:
|
||||
if wants_firewall_inventory(title, issue):
|
||||
return list(PFSENSE_FIREWALL_DIAGNOSTICS)
|
||||
return list(PFSENSE_DEFAULT_DIAGNOSTICS)
|
||||
|
||||
|
||||
async def run_pfsense_diagnostics(
|
||||
task_id: int,
|
||||
device: dict,
|
||||
manager,
|
||||
emit: EmitFn,
|
||||
*,
|
||||
title: str = "",
|
||||
issue: str = "",
|
||||
max_diagnostics: int = 8,
|
||||
) -> list[dict]:
|
||||
"""Connect to pfSense and run read-only diagnostics matched to the task."""
|
||||
from app.agent.device_connect import pfsense_connect_args
|
||||
|
||||
results: list[dict] = []
|
||||
label = device.get("catalog_key") or device.get("name") or "pfsense"
|
||||
server = PFSENSE_MCP
|
||||
|
||||
server_state = manager.servers.get(server or "")
|
||||
if not server_state or server_state.status != "loaded":
|
||||
await emit(
|
||||
task_id,
|
||||
"diagnose",
|
||||
f"MCP '{server}' not available for {label}.",
|
||||
{"device": label, "server": server},
|
||||
)
|
||||
return results
|
||||
|
||||
diagnostics = diagnostics_for_issue(title, issue)
|
||||
intent = "firewall inventory" if wants_firewall_inventory(title, issue) else "health"
|
||||
|
||||
await emit(
|
||||
task_id,
|
||||
"connect",
|
||||
f"Connecting to {label} via {server} ({intent})...",
|
||||
{"device": label, "intent": intent},
|
||||
)
|
||||
|
||||
ok = await invoke_connect(
|
||||
task_id, label, server, "pfsense_connect", pfsense_connect_args(device), manager, emit
|
||||
)
|
||||
if not ok:
|
||||
return results
|
||||
|
||||
for tool, builder in diagnostics[:max_diagnostics]:
|
||||
args = builder(device)
|
||||
res, tool_ok = await invoke_tool(
|
||||
task_id,
|
||||
label,
|
||||
server,
|
||||
tool,
|
||||
args,
|
||||
manager,
|
||||
emit,
|
||||
task_title=title,
|
||||
task_issue=issue,
|
||||
)
|
||||
results.append(
|
||||
{
|
||||
"device": label,
|
||||
"tool": tool,
|
||||
"ok": tool_ok,
|
||||
"output": res.get("text", ""),
|
||||
"intent": intent,
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
Reference in New Issue
Block a user