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>
94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
export const ACTIVE_STATUSES = ["running", "queued", "waiting_approval"] as const;
|
|
|
|
export type TaskStatus =
|
|
| "queued"
|
|
| "running"
|
|
| "waiting_approval"
|
|
| "succeeded"
|
|
| "failed"
|
|
| "cancelled";
|
|
|
|
export type TaskEvent = {
|
|
id?: number;
|
|
kind: string;
|
|
message: string | null;
|
|
payload?: Record<string, any> | null;
|
|
ts?: string;
|
|
created_at?: string;
|
|
replay?: boolean;
|
|
};
|
|
|
|
export type TaskOverview = {
|
|
id: number;
|
|
title: string;
|
|
issue: string;
|
|
status: TaskStatus;
|
|
vessel_id: number | null;
|
|
vessel_name?: string | null;
|
|
summary?: string | null;
|
|
key_finding?: string | null;
|
|
devices_checked: string[];
|
|
resolved?: boolean | null;
|
|
memory_id?: string | null;
|
|
obsidian_path?: string | null;
|
|
obsidian_push_error?: string | null;
|
|
run_cost_usd?: number | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type TaskPreview = {
|
|
vessel_id: number;
|
|
vessel_name?: string | null;
|
|
devices: { catalog_key: string; name: string; label: string; checks: string[] }[];
|
|
rule_id?: string | null;
|
|
rule_name?: string | null;
|
|
severity?: string | null;
|
|
hints: string[];
|
|
warning?: string | null;
|
|
};
|
|
|
|
export function isActiveStatus(status: string) {
|
|
return ACTIVE_STATUSES.includes(status as (typeof ACTIVE_STATUSES)[number]);
|
|
}
|
|
|
|
export function taskPhaseLabel(status: string, events: TaskEvent[] = []) {
|
|
if (status === "queued") return "Waiting in queue";
|
|
if (status === "waiting_approval") return "Waiting for approval";
|
|
if (status === "succeeded") return "Report complete";
|
|
if (status === "failed") return "Failed";
|
|
if (status === "cancelled") return "Stopped";
|
|
const last = [...events].reverse().find((e) => e.kind !== "log");
|
|
if (!last) return "Starting";
|
|
if (last.kind === "context") return "Gathering context";
|
|
if (last.kind === "triage" || last.kind === "rule") return "Planning checks";
|
|
if (last.kind === "connect") return "Connecting to devices";
|
|
if (last.kind === "tool_start" || last.kind === "tool_call" || last.kind === "diagnose") {
|
|
const device = last.payload?.device;
|
|
const tool = humanToolName(String(last.payload?.tool || ""));
|
|
return [device, tool].filter(Boolean).join(": ") || "Running diagnostics";
|
|
}
|
|
if (last.kind === "reason") return "Analyzing findings";
|
|
if (last.kind === "report") return "Writing report";
|
|
return last.message || "Running";
|
|
}
|
|
|
|
export function humanToolName(tool: string) {
|
|
const known: Record<string, string> = {
|
|
pfsense_get_system_status: "pfSense system status",
|
|
pfsense_list_interfaces: "pfSense interfaces",
|
|
pfsense_list_gateways: "pfSense gateways",
|
|
pfsense_get_gateway_status: "pfSense gateway status",
|
|
pfsense_list_firewall_rules: "pfSense firewall rules",
|
|
pfsense_list_port_forwards: "pfSense port forwards",
|
|
pfsense_list_outbound_nat_mappings: "pfSense outbound NAT",
|
|
proxmox_list_nodes: "Proxmox nodes",
|
|
proxmox_list_qemu: "Proxmox VMs",
|
|
proxmox_list_lxc: "Proxmox LXCs",
|
|
proxmox_get_qemu_status: "VM status",
|
|
proxmox_get_qemu_config: "VM config",
|
|
ssh_run: "SSH command",
|
|
};
|
|
return known[tool] || tool.replace(/_/g, " ");
|
|
}
|