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

315
backend/app/schemas.py Normal file
View File

@@ -0,0 +1,315 @@
"""Pydantic request/response schemas."""
from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, EmailStr
from app.models.enums import ApprovalStatus, DeviceType, Role, TaskStatus
# ---- Auth / users -----------------------------------------------------
class Token(BaseModel):
access_token: str
token_type: str = "bearer"
role: Role
email: str
class UserCreate(BaseModel):
email: EmailStr
password: str
full_name: str | None = None
role: Role = Role.readonly
class UserUpdate(BaseModel):
full_name: str | None = None
role: Role | None = None
is_active: bool | None = None
password: str | None = None
class UserOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
email: str
full_name: str | None
role: Role
is_active: bool
created_at: datetime
# ---- Inventory (vessels + onboard devices) ------------------------------
class CatalogEntryOut(BaseModel):
key: str
label: str
description: str
device_type: str
mcp_server: str | None
default_port: int
default_username: str | None
has_default_secret: bool = False
secret_kind: str = "password" # password | api_key
env_defaults_configured: bool = False
asterisk_container: str | None = None # GeneseasX docker container name (e.g. voip)
category: str
class VesselDeviceSelection(BaseModel):
"""One catalog slot when creating/updating a vessel."""
catalog_key: str
enabled: bool = True
port: int | None = None # falls back to catalog default_port
address: str | None = None # falls back to vessel public_ip
username: str | None = None
secret: str | None = None
notes: str | None = None
class VesselCreate(BaseModel):
name: str
site: str | None = None
public_ip: str | None = None
notes: str | None = None
devices: list[VesselDeviceSelection] = []
class VesselUpdate(BaseModel):
name: str | None = None
site: str | None = None
public_ip: str | None = None
notes: str | None = None
devices: list[VesselDeviceSelection] | None = None
class DeviceOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
catalog_key: str | None
device_type: DeviceType
address: str
port: int | None
mcp_server: str | None
username: str | None
has_secret: bool = False
notes: str | None
vessel_id: int | None
created_at: datetime
class VesselOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
site: str | None
public_ip: str | None
notes: str | None
created_at: datetime
class VesselDetail(VesselOut):
devices: list[DeviceOut] = []
class DeviceUpdate(BaseModel):
port: int | None = None
address: str | None = None
username: str | None = None
secret: str | None = None
notes: str | None = None
# ---- Tasks ------------------------------------------------------------
class TaskCreate(BaseModel):
title: str
issue: str
device_id: int | None = None
vessel_id: int | None = None
details: dict[str, Any] | None = None
class TaskPreviewIn(BaseModel):
title: str = ""
issue: str
vessel_id: int
class TaskPreviewDevice(BaseModel):
catalog_key: str
name: str
label: str
checks: list[str] = []
class TaskPreviewOut(BaseModel):
vessel_id: int
vessel_name: str | None = None
devices: list[TaskPreviewDevice]
rule_id: str | None = None
rule_name: str | None = None
severity: str | None = None
hints: list[str] = []
warning: str | None = None
class TaskContinue(BaseModel):
follow_up: str
class TaskEscalateLlm(BaseModel):
tier: Literal["economy", "premium"]
class TaskEventOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
kind: str
message: str | None
payload: dict[str, Any] | None
created_at: datetime
class TaskOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
title: str
issue: str
details: dict[str, Any] | None
status: TaskStatus
device_id: int | None
vessel_id: int | None
created_by_id: int | None
report: dict[str, Any] | None
error: str | None
memory_id: str | None
obsidian_path: str | None
vessel_name: str | None = None
created_at: datetime
updated_at: datetime
class TaskOverviewOut(BaseModel):
id: int
title: str
issue: str
status: TaskStatus
vessel_id: int | None = None
vessel_name: str | None = None
summary: str | None = None
key_finding: str | None = None
devices_checked: list[str] = []
resolved: bool | None = None
memory_id: str | None = None
obsidian_path: str | None = None
obsidian_push_error: str | None = None
run_cost_usd: float | None = None
created_at: datetime
updated_at: datetime
class TaskDetail(TaskOut):
events: list[TaskEventOut] = []
class ApprovalOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
task_id: int
tool_name: str
tool_args: dict[str, Any] | None
risk: str | None
status: ApprovalStatus
created_at: datetime
class ApprovalDecision(BaseModel):
approve: bool
# ---- MCP --------------------------------------------------------------
class McpToolOut(BaseModel):
name: str
description: str | None = None
class McpServerOut(BaseModel):
name: str
status: str # loaded | error | cloning | stopped
tools: list[McpToolOut] = []
error: str | None = None
source: str | None = None # local | git
class McpCommandResult(BaseModel):
ok: bool
action: str | None = None
server: str | None = None
error: str | None = None
class McpDevelopRequest(BaseModel):
issue: str
tool: str | None = None
task_id: int | None = None
push: bool | None = None
class McpDevelopResult(BaseModel):
ok: bool
task_id: int | None = None
summary: str | None = None
error: str | None = None
tools: list[str] | None = None
push: dict | None = None
# ---- Balance ----------------------------------------------------------
class BalanceOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
provider: str
total_credits: float | None
total_usage: float | None
remaining: float | None
currency: str
error: str | None
created_at: datetime
# ---- LLM routing ------------------------------------------------------
class LlmRoutingConfigIn(BaseModel):
local_model_id: str | None = None
economy_gemini_id: str | None = None
economy_deepseek_id: str | None = None
economy_openrouter_id: str | None = None
premium_gemini_id: str | None = None
premium_deepseek_id: str | None = None
premium_openrouter_id: str | None = None
cloud_provider_order: list[str] | None = None
auto_escalate: bool | None = None
max_tier: str | None = None
class ProviderModelsOut(BaseModel):
providers: dict[str, Any]
routing_order: str
class LlmRoutingConfigOut(BaseModel):
config: dict[str, Any]
catalog: dict[str, Any]
available_backends: dict[str, bool]
strategy: str
# ---- Troubleshooting rules --------------------------------------------
class TroubleshootingRulesUpdate(BaseModel):
yaml: str
class TroubleshootingRulesOut(BaseModel):
yaml: str
parsed: dict[str, Any]
path_hint: str