Single Source of Truth: Frontend/Backend-Ungereimtheiten ausgeraeumt (Review 15.07.)

Backend (Wahrheit reparieren):
- gateway_reachable() prueft jetzt den ECHTEN mc2-gateway (:9010 via V1_UPSTREAM)
  statt nur die Engine - toter Denkpfad war in Health/Cockpit unsichtbar
- /api/system/services vollstaendig: + LLM-Gateway, + Steuerpult, + Waechter
  (mc2-steward via is-active); scope-Feld (user/system); mc2-gateway/mc2-steward
  in die Restart-Allowlist
- Connect-Health Leitung 1 prueft den Client-Pfad (V1_UPSTREAM statt llama-swap)
- agent_status liefert pc_executor_url (UI zeigte hartcodierte IP/Ports)
- SSE-Endlosschleife gefixt: /api/auftragsbuch schrieb announce-branches bei JEDEM
  Aufruf neu -> mtime-Bump -> invalidate -> Refetch im 3-s-Takt je Client;
  jetzt nur noch bei echter Aenderung

Frontend (ein Datenweg):
- SystemDrawer komplett auf React-Query-Hooks (vorher eigenes 3-s-setInterval auf
  dieselben Endpunkte inkl. teurem Updates-Check); Dienste-Liste = Backend-Antwort
  (SERVICES-UI-Kopie geloescht); useDialog statt Eigenbau; fmtBytes statt Duplikat
- useLucyHealth: Dienst-Matching per systemd-unit statt Namensfragment; Gateway-
  Reparatur zielt auf mc2-gateway; neuer Waechter-Check; Backend-tot => ehrliches
  rotes Verdikt statt eingefrorener letzter Stand (auch Sidebar)
- Toter Code raus: views/models/Cockpit.tsx (908 Z.) + RoleAssignModal + Placeholder;
  LaneEditor (Routing-Policy) + WarmSetManager damit ZURUECK im UI als Tab
  "Routing & Warm-Set" im Modell-Manager
- Cockpit: blockierte Ideen-Karten erscheinen in "Braucht dich" + Kachel-Hint;
  Verbinden-Kachel zeigt 3 Leitungen live statt hartem "Zed"
- Maschinenraum: ehrliches "frei" (reale Belegung) + eigenes KV-Cache-Segment
- AgentView: Ports/PC-Adresse aus der API; Guide lehrt Lanes chat/coding
- queries.ts: useModels nutzt SSE-relax; Chronik-Limit im Query-Key;
  useJobs/useZeitmaschine mit enabled-Schalter

Verifiziert: tsc+vite gruen, Backend-Smoke beide Gateway-Modi, UI live gegen die
Box (Cockpit/Werkbank/Routing-Tab/Drawer rendern mit Echtdaten).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-15 22:22:27 +02:00
parent b36561990e
commit 0c69c491e5
161 changed files with 1522 additions and 2458 deletions
+9 -1
View File
@@ -270,8 +270,15 @@ export interface SystemStatus {
versions?: Versions
}
export interface ServiceInfo {
name: string
unit: string // echter systemd-Name — Basis für Restart/Logs/Matching (kein Namens-Raten)
url: string
ok: boolean
scope: "user" | "system"
}
export interface ServicesResp {
services: { name: string; unit: string; url: string; ok: boolean }[]
services: ServiceInfo[]
links: { engine_ui: string; gateway: string; hermes_ui: string }
}
@@ -387,6 +394,7 @@ export interface AgentStatus {
telegram_enabled?: boolean
mcp_server_count?: number
pc_executor_reachable?: boolean
pc_executor_url?: string
}
export interface Job {
+12 -5
View File
@@ -111,9 +111,11 @@ export const useIdeenLog = (id: string, enabled: boolean = false) =>
staleTime: 0,
})
// Limit gehört in den Key: zwei Aufrufer mit verschiedenen Limits würden sich sonst
// denselben Cache-Eintrag gegenseitig überschreiben. SSE invalidiert das Präfix ["chronik"].
export const useChronik = (limit = 150, refetchInterval: number = TAKT.gemuetlich) =>
useQuery({
queryKey: qk.chronik,
queryKey: [...qk.chronik, limit],
queryFn: () => api<ChronikResp>(`/api/chronik?limit=${limit}`),
refetchInterval: relax(refetchInterval),
select: (d) => d.items ?? [],
@@ -129,11 +131,12 @@ export const useEigenleben = (refetchInterval: number = TAKT.traege) =>
refetchInterval,
})
export const useZeitmaschine = (refetchInterval: number = TAKT.traege) =>
export const useZeitmaschine = (refetchInterval: number = TAKT.traege, enabled = true) =>
useQuery({
queryKey: qk.zeitmaschine,
queryFn: () => api<ZeitmaschineResp>("/api/zeitmaschine"),
refetchInterval,
enabled,
})
export const useReminders = (refetchInterval: number = TAKT.traege) =>
@@ -169,8 +172,9 @@ export const useSystemStatus = (refetchInterval: number = TAKT.graph) =>
export const useServices = (refetchInterval: number = TAKT.normal) =>
useQuery({ queryKey: qk.services, queryFn: () => api<ServicesResp>("/api/system/services"), refetchInterval })
// Der SSE-Strom invalidiert "models" bei Laden/Entladen → Poller ist nur Sicherheitsnetz.
export const useModels = (refetchInterval: number = TAKT.schnell) =>
useQuery({ queryKey: qk.models, queryFn: () => api<ModelsResp>("/api/models"), refetchInterval })
useQuery({ queryKey: qk.models, queryFn: () => api<ModelsResp>("/api/models"), refetchInterval: relax(refetchInterval) })
export const useGroups = (refetchInterval: number = TAKT.normal) =>
useQuery({ queryKey: qk.groups, queryFn: () => api<GroupsResp>("/api/groups"), refetchInterval })
@@ -182,11 +186,14 @@ export const useRoutingPolicy = () =>
useQuery({ queryKey: qk.routingPolicy, queryFn: () => api<RoutingPolicyMeta>("/api/routing/policy") })
// Jobs (Downloads) takten etwas schneller, damit Fortschritts-Balken flüssig wirken.
export const useJobs = (refetchInterval = 3_000) =>
// `enabled=false` pausiert den Beobachter (z. B. geschlossene System-Schublade) —
// der Cache bleibt geteilt, es pollt nur niemand umsonst.
export const useJobs = (refetchInterval = 3_000, enabled = true) =>
useQuery({
queryKey: qk.jobs,
queryFn: () => api<{ jobs: Job[] }>("/api/jobs"),
refetchInterval,
enabled,
select: (d) => d.jobs ?? [],
})
@@ -242,7 +249,7 @@ export const useMemory = (opts?: { q?: string; category?: string; limit?: number
})
/** Nach Mutationen die betroffenen Listen-Queries neu ziehen. */
export function invalidate(qc: QueryClient, ...keys: readonly unknown[][]) {
export function invalidate(qc: QueryClient, ...keys: (readonly unknown[])[]) {
for (const key of keys) qc.invalidateQueries({ queryKey: key })
}
+50 -13
View File
@@ -2,7 +2,7 @@
// Lucy-Fähigkeiten in Klartext, inkl. Gesamt-Verdikt, Speicher-Ampel und einer
// konkreten 1-Klick-Reparatur pro Problem. EINE Quelle für das Status-Panel.
import { Brain, BookMarked, Eye, Plug, Bot, Mic, type LucideIcon } from "lucide-react"
import { Brain, BookMarked, Eye, Plug, Bot, Mic, ShieldAlert, Server, type LucideIcon } from "lucide-react"
import { useHealth, useServices, useModels, useSystemStatus } from "./queries"
export type CheckStatus = "ok" | "warn" | "down" | "loading"
@@ -40,17 +40,25 @@ export interface LucyHealth {
memory: MemoryAmpel
problems: LucyCheck[] // kritische Down-Checks (für den Schnellzugriff im Banner)
warns: LucyCheck[] // nicht-kritische Hinweise — damit das Banner SAGT, was los ist
unreachable: boolean // Zentrale (MC2) antwortet nicht → alle anderen Anzeigen sind veraltet
}
export function useLucyHealth(): LucyHealth {
const { data: health } = useHealth()
const { data: svc } = useServices()
const { data: health, isError: healthErr } = useHealth()
const { data: svc, isError: svcErr } = useServices()
const { data: modelsResp } = useModels()
const { data: sys } = useSystemStatus()
// React Query behält bei Fehlern die letzten Daten — ohne diesen Riegel zeigte die UI
// bei totem Backend dauerhaft den letzten grünen Stand („eingefrorene Ampel", Review 15.07.).
const unreachable = healthErr || svcErr
const loaded = !!health && !!svc
const svcOk = (frag: string): boolean | undefined =>
svc?.services.find((s) => s.name.toLowerCase().includes(frag))?.ok
// Matching über den ECHTEN systemd-Unit-Namen (Backend liefert `unit`) — vorher wurde
// fragil im deutschen Anzeige-Namen gesucht ("gateway (integr" …).
const svcOk = (unit: string): boolean | undefined =>
svc?.services.find((s) => s.unit === unit)?.ok
const svcHas = (unit: string): boolean => !!svc?.services.some((s) => s.unit === unit)
const engineOk = health?.engine_reachable
const brainReady = health?.brain?.ready
@@ -61,6 +69,15 @@ export function useLucyHealth(): LucyHealth {
const checks: LucyCheck[] = []
// 0) Die Zentrale selbst. Antwortet MC2 nicht, ist ALLES andere Spekulation —
// dieser Check übertrumpft die übrigen (die zeigen sonst eingefrorene alte Werte).
if (unreachable) {
checks.push({
id: "mc2", label: "Steuerpult (MC2)", icon: Server, critical: true, status: "down",
detail: "Die Zentrale antwortet nicht — alle Anzeigen hier können veraltet sein. Läuft die Box? Deploy aktiv?",
})
}
// 1) Lucys Hirn — die wichtigste Fähigkeit. Engine läuft UND das Hirn-Modell ist geladen?
checks.push(((): LucyCheck => {
const base = { id: "brain", label: "Lucys Hirn", icon: Brain, critical: true }
@@ -107,7 +124,7 @@ export function useLucyHealth(): LucyHealth {
// 3) Lucys Gedächtnis — Mem0-Dienst.
checks.push(((): LucyCheck => {
const base = { id: "memory", label: "Lucys Gedächtnis", icon: BookMarked, critical: true }
const ok = svcOk("mem0")
const ok = svcOk("mem0-service")
if (!loaded || ok === undefined) return { ...base, status: "loading", detail: "Wird geprüft …" }
return ok
? { ...base, status: "ok", detail: "Merkt sich Neues und findet Altes wieder." }
@@ -119,25 +136,45 @@ export function useLucyHealth(): LucyHealth {
}
})())
// 4) Verbindung (Gateway) — der integrierte OpenAI-Gateway, über den Apps/IDEs Lucy erreichen.
// 4) Verbindung (LLM-Gateway) — der Denkpfad, über den Apps/IDEs/Lucy die Modelle erreichen.
// Seit UMBAU v3 ist das der EIGENE mc2-gateway-Prozess; health.gateway_reachable prüft
// ihn jetzt wirklich (statt nur die Engine). Repariert wird die richtige Unit.
checks.push(((): LucyCheck => {
const base = { id: "gateway", label: "Verbindung (Gateway)", icon: Plug, critical: true }
const ok = health ? health.gateway_reachable : svcOk("integriert")
const ok = health ? health.gateway_reachable : svcOk("mc2-gateway")
if (!loaded || ok === undefined) return { ...base, status: "loading", detail: "Wird geprüft …" }
const unit = svcHas("mc2-gateway") ? "mc2-gateway" : "mission-control-2"
return ok
? { ...base, status: "ok", detail: "Apps und IDEs können Lucy erreichen." }
: {
...base,
status: "down",
detail: "Die Verbindungs-Zentrale antwortet nicht — Apps erreichen Lucy nicht.",
repair: { kind: "restart", service: "mission-control-2", label: "Zentrale neu starten" },
detail: "Der Modell-Gateway antwortet nicht — Lucy und die IDEs erreichen kein Modell.",
repair: { kind: "restart", service: unit, label: "Gateway neu starten" },
}
})())
// 4b) Der Wächter (Steward) — überwacht Warm-Set, Health-Flanken und Gedächtnis-Dubletten.
// Nicht kritisch fürs Antworten, aber ohne ihn merkt niemand, wenn etwas kippt.
checks.push(((): LucyCheck => {
const base = { id: "steward", label: "Wächter (Steward)", icon: ShieldAlert, critical: false }
if (!loaded) return { ...base, status: "loading", detail: "Wird geprüft …" }
if (!svcHas("mc2-steward"))
return { ...base, status: "ok", detail: "Auf dieser Installation nicht eingerichtet." }
return svcOk("mc2-steward")
? { ...base, status: "ok", detail: "Wacht über Warm-Set, Dienste und Gedächtnis." }
: {
...base,
status: "warn",
detail: "Der Wächter schläft — niemand meldet gerade Ausfälle oder wärmt Modelle nach.",
repair: { kind: "restart", service: "mc2-steward", label: "Wächter neu starten" },
}
})())
// 5) Lucys Agent (Hermes) — der Dienst, der als Lucy antwortet (Terminal/Telegram/Sprechen).
checks.push(((): LucyCheck => {
const base = { id: "agent", label: "Lucys Agent (Hermes)", icon: Bot, critical: true }
const ok = svcOk("hermes-gateway")
const ok = svcOk("hermes-gateway") // unit-Name (identisch zum alten Namens-Match, jetzt exakt)
if (!loaded || ok === undefined) return { ...base, status: "loading", detail: "Wird geprüft …" }
return ok
? { ...base, status: "ok", detail: "Lucy ist bereit zu reden und zu handeln." }
@@ -152,7 +189,7 @@ export function useLucyHealth(): LucyHealth {
// 6) Lucys Stimme — Voice-Dienst (STT/TTS). Nicht kritisch: Chat geht auch ohne.
checks.push(((): LucyCheck => {
const base = { id: "voice", label: "Lucys Stimme", icon: Mic, critical: false }
const ok = svcOk("voice")
const ok = svcOk("voice-service")
if (!loaded || ok === undefined) return { ...base, status: "loading", detail: "Wird geprüft …" }
return ok
? { ...base, status: "ok", detail: "Lucy kann hören und sprechen." }
@@ -194,5 +231,5 @@ export function useLucyHealth(): LucyHealth {
? "warn"
: "gut"
return { verdict, checks, memory, problems, warns }
return { verdict, checks, memory, problems, warns, unreachable }
}