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:
2026-06-14 22:11:07 +03:00
commit 6185b9b85a
126 changed files with 14565 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
"""Per-device-type connection and read-only diagnostic playbooks.
Each entry maps a DeviceType to:
- mcp_server: default MCP server that drives the device
- connect: (tool_name, arg_builder) to establish a session (optional)
- diagnostics: list of (tool_name, arg_builder) read-only calls to gather state
arg_builder is a callable(device_ctx) -> dict, where device_ctx contains
address, port, username, secret.
"""
from __future__ import annotations
from typing import Callable
from app.agent.asterisk_profiles import ASTERISK_PLAYBOOKS
from app.agent.device_connect import pfsense_connect_args, ssh_connect_args
from app.models.enums import DeviceType
ArgBuilder = Callable[[dict], dict]
def _ip(ctx: dict) -> dict:
return {"ip": ctx["address"]}
def _pfsense_connect(ctx: dict) -> dict:
return pfsense_connect_args(ctx)
def _ssh(ctx: dict) -> dict:
return ssh_connect_args(ctx)
PLAYBOOKS: dict[DeviceType, dict] = {
DeviceType.pfsense: {
"mcp_server": "pfsense-mcp",
"connect": ("pfsense_connect", _pfsense_connect),
"diagnostics": [
("pfsense_get_system_status", lambda c: {}),
("pfsense_list_gateways", lambda c: {}),
("pfsense_get_gateway_status", lambda c: {}),
("pfsense_list_interfaces", lambda c: {}),
],
},
DeviceType.proxmox: {
# Connect + diagnostics handled by proxmox_profiles.run_proxmox_diagnostics (API → SSH).
"mcp_server": "proxmox-mcp",
"connect": None,
"diagnostics": [],
},
DeviceType.asterisk: {
# Legacy key — prefer asterisk_geneseasx catalog slot (see ASTERISK_PLAYBOOKS).
"mcp_server": "ssh-generic-mcp",
"connect": ("ssh_connect", _ssh),
"diagnostics": [],
},
# GeneseasX / satbox variants are resolved by catalog_key in playbook_for().
DeviceType.debian: {
"mcp_server": "ssh-generic-mcp",
"connect": (
"ssh_connect",
lambda c: ssh_connect_args(c),
),
"diagnostics": [
("ssh_run", lambda c: {"command": "uptime && uname -a"}),
("ssh_run", lambda c: {"command": "df -h"}),
("ssh_run", lambda c: {"command": "free -m"}),
("ssh_run", lambda c: {"command": "systemctl --failed --no-pager"}),
("ssh_run", lambda c: {"command": "journalctl -p err -n 100 --no-pager"}),
],
},
DeviceType.geneseasx: {
"mcp_server": "ssh-generic-mcp",
"connect": (
"ssh_connect",
lambda c: ssh_connect_args(c),
),
"diagnostics": [
("ssh_run", lambda c: {"command": "uptime && uname -a"}),
("ssh_run", lambda c: {"command": "docker ps --format '{{.Names}}: {{.Status}}'"}),
("ssh_run", lambda c: {"command": "systemctl --failed --no-pager"}),
],
},
DeviceType.fortigate: {
"mcp_server": "fortigate-mcp",
"connect": (
"fortigate_connect",
lambda c: {
"host": c["address"],
"api_key": c.get("secret"),
"username": c.get("username"),
},
),
"diagnostics": [
("fortigate_get_system_status", lambda c: {}),
("fortigate_list_interfaces", lambda c: {}),
("fortigate_list_policies", lambda c: {}),
("fortigate_get_logs", lambda c: {"lines": 100}),
],
},
DeviceType.fortiswitch: {
"mcp_server": "fortiswitch-mcp",
"connect": (
"fortiswitch_connect",
lambda c: {
"host": c["address"],
"username": c.get("username"),
"password": c.get("secret"),
},
),
"diagnostics": [
("fortiswitch_get_system_status", lambda c: {}),
("fortiswitch_list_ports", lambda c: {}),
("fortiswitch_get_port_stats", lambda c: {}),
],
},
}
def playbook_for(
device_type: DeviceType,
mcp_override: str | None,
catalog_key: str | None = None,
) -> dict | None:
if catalog_key and catalog_key in ASTERISK_PLAYBOOKS:
pb = ASTERISK_PLAYBOOKS[catalog_key]
else:
pb = PLAYBOOKS.get(device_type)
if pb is None:
return None
# Catalog playbooks define the MCP server; ignore stale device-row overrides.
if mcp_override and catalog_key not in ASTERISK_PLAYBOOKS and device_type != DeviceType.proxmox:
pb = {**pb, "mcp_server": mcp_override}
return pb