Files
mission-control-v2/frontend/src/lib/queries.ts
T
HitonabiandClaude Opus 5 84dc34c0a3 fix(stack): Mem0 restlos ausgebaut + vier stille Defekte behoben
Systemaudit vom 27.08.2026. Alle Befunde gemessen, nicht vermutet.

VIER STILLE DEFEKTE

1. mc2-steward startete seit Wochen nicht (live: 207.609 Neustarts).
   steward.py importierte services.memory, das beim Mem0-Ausbau geloescht
   wurde -> ImportError bei jedem Start. Re-Warm- und Health-Waechter
   waren damit tot.

2. Jedes Hermes-Update wurde automatisch zurueckgerollt.
   hermes-postcheck.sh prueft vier Dinge, die es seit dem 07.08. nicht mehr
   gibt (Sidecar :8765, /api/memory, memory.provider, mc2-memory-Plugin).
   Die Checks konnten nicht gruen werden -> autoupdate.sh wertete jedes
   Update als rot und rollte es zurueck. Checks ersatzlos entfernt; der
   Tool-Smoke laeuft ohnehin durch den echten Agenten.

3. 7 von 12 Skills waren per Knopfdruck nicht startbar.
   deploy.sh kopiert Skills mit tr '-' '_' nach ~/.hermes/skills/,
   routers/skills.py gab Hermes aber den Ordnernamen MIT Bindestrich.
   Der Knopf meldete Erfolg, ausgefuehrt wurde nichts. Neu: _hermes_name().

4. deploy.sh warf bei jedem Deploy die Live-Modellkonfiguration weg.
   MC2 schreibt /etc/llama-swap/config.yaml selbst; die Repo-Datei ist nur
   ein Abzug (ihm fehlt u.a. kritiker/Devstral). Jetzt: erst sichern, Diff
   zeigen, dann kopieren. MC_DEPLOY_SKIP_SWAP_CONFIG=1 ueberspringt.

MEM0-AUSBAU VOLLENDET (Kriterium 3: 17 -> 0 Dateien)
- mem0_service/, mcp/mcp_memory.py und hermes/plugins/mc2-memory entfernt;
  das Plugin schickte bei JEDEM Turn zwei 404-Requests an tote Routen.
- MEMORY_DB/MEM0_SERVICE_URL, _mem0_reachable(), MC_MEMORY_DB und
  MC_MEM_DEDUPE_ENABLED aus Config/Router/Unit entfernt.
- mem0_ms war strukturell tot (park("retrieve") wird nirgends mehr
  aufgerufen) -> aus Backend, API-Typ und Latenzkarte entfernt.
- Verbinden-Tab: tote Gedaechtnis-MCP-Leitung raus, Status-Kachel bleibt.
- AGENTS.md beschrieb Mem0 noch als aktiv - korrigiert.

GATEWAY-ROBUSTHEIT
- _proxy gab bei ungueltigen Payloads HTTP 500 (gemessen 5/5: Rohtext,
  leerer Body, JSON-Liste, JSON-String, null) -> jetzt 5/5 HTTP 400.
- Bild-Weiche ohne Deckel: 10 Bilder x 2 Versuche x 240 s hielten den
  Client bis zu 80 min. Neu: MC_CODER_IMAGE_MAX (4), Rueckfall auf die
  Vision-Umleitung.
- /v1/models: nicht-JSON von der Engine gab 500 -> jetzt 502.

UNITS UND DEPLOY
- mc2-steward.service, dessen warmset-Drop-in und voice-service.service
  fehlten im Repo, obwohl maintenance.py und stack-postcheck.sh sie
  voraussetzen. 1:1 von der laufenden Box uebernommen.
- deploy.sh startete mc2-steward nie neu; restore.sh liess mc2-gateway und
  mc2-steward mit alter Config weiterlaufen. Beide ergaenzt.

FRONTEND
- useEigenleben rief /api/eigenleben - existiert im Backend nicht und wurde
  nirgends genutzt. Samt Typen entfernt.
- Anleitung beschrieb einen Gedaechtnis-Tab, den es nicht gibt.
- Abgeglichen: alle uebrigen 63 Frontend-Aufrufe treffen echte Routen, alle
  5 SSE-Invalidation-Keys sind gemappt, keine ungefangenen Promises.

Gates: compileall gruen - ruff "All checks passed" - tsc gruen - vite build
gruen (dist aktualisiert) - Importe app/steward/gateway_app gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:58:42 +02:00

246 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Zentrale Daten-Hooks (TanStack Query). Kapseln lib/api.ts und liefern Caching,
// Dedup (gleicher queryKey = eine Anfrage über alle Views), Retry und Polling.
// Mutations invalidieren gezielt die betroffenen Keys, statt manuell neu zu laden.
import { useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query"
import { sseVerbunden } from "./events"
import {
api,
type AgentStatus,
type AuftragsbuchResp,
type ChronikResp,
type ConnectResp,
type ConnectHealth,
type DiscoverResp,
type DraftsResp,
type GroupsResp,
type HermesBrainResp,
type Health,
type HistoryResp,
type IdeenLogResp,
type IdeenResp,
type Job,
type MemoryGraph,
type ModelsResp,
type Reminder,
type RoutingResp,
type RoutingPolicyMeta,
type ServicesResp,
type SystemStatus,
type TokenStats,
type UpdatesResp,
type VoiceTraceResp,
type WissenResp,
type ZeitmaschineResp,
} from "./api"
// Zentrale Query-Keys (eine Quelle der Wahrheit für invalidate).
export const qk = {
health: ["health"] as const,
systemStatus: ["system-status"] as const,
services: ["services"] as const,
models: ["models"] as const,
groups: ["groups"] as const,
routing: ["routing"] as const,
routingPolicy: ["routing-policy"] as const,
jobs: ["jobs"] as const,
tokenStats: ["token-stats"] as const,
agentStatus: ["agent-status"] as const,
hermesBrain: ["hermes-brain"] as const,
updates: ["updates"] as const,
discover: ["discover"] as const,
drafts: (target?: string) => ["drafts", target ?? ""] as const,
connect: (params?: string) => ["connect", params ?? ""] as const,
connectHealth: ["connect-health"] as const,
memoryGraph: ["memory-graph"] as const,
voiceTrace: ["voice-trace"] as const,
auftragsbuch: ["auftragsbuch"] as const,
ideen: ["ideen"] as const,
chronik: ["chronik"] as const,
wissen: ["wissen"] as const,
zeitmaschine: ["zeitmaschine"] as const,
reminders: ["reminders"] as const,
}
// ── Polling-Takte ────────────────────────────────────────────────────────────
// EINE Stelle für alle Intervalle. Regel: Die Live-Graphen (System/Token) takten
// schnell, alles andere gemächlich — Mutationen invalidieren ohnehin gezielt,
// Polling ist nur das Sicherheitsnetz. Komponenten übergeben KEINE eigenen
// Intervalle mehr (mehrere Beobachter = kleinstes Intervall gewinnt).
const TAKT = {
graph: 3_000, // Live-Graphen im Cockpit (System, Token-Durchsatz)
schnell: 8_000, // „fühlt sich live an": Modelle, Voice-Trace, Auftragsbuch
normal: 10_000, // Dienste, Routing, Agent-Status, Health
gemuetlich: 30_000, // Chronik, Ideen
traege: 60_000, // Updates-Check, Reminders, Zeitmaschine, HF-Abfragen
} as const
// UMBAU v3 P3a: Läuft der SSE-Eventstrom (/api/events, siehe lib/events.ts), sind die
// Poller der ereignishaften Quellen nur noch SICHERHEITSNETZ — der Strom invalidiert
// bei Änderung sofort. Verbunden = Takt ×5; Strom weg = alter Takt (EventSource
// reconnectet selbst). Live-Graphen/Status bleiben bewusst beim Polling.
const relax = (ms: number) => () => (sseVerbunden() ? ms * 5 : ms)
// Auftragsbuch: Vorschlags-Inbox (Branches + Skill-Kandidaten). Pollt, damit laufende
// Annahme-Läufe (Status aus der JSON-Datei des Runners) live sichtbar werden.
export const useAuftragsbuch = (refetchInterval: number = TAKT.schnell) =>
useQuery({
queryKey: qk.auftragsbuch,
queryFn: () => api<AuftragsbuchResp>("/api/auftragsbuch"),
refetchInterval: relax(refetchInterval),
})
// Ideen-Queue (natives Hermes-Kanban): das Backend cached ~15 s, öfter pollen lohnt nicht.
export const useIdeen = (refetchInterval: number = TAKT.gemuetlich) =>
useQuery({
queryKey: qk.ideen,
queryFn: () => api<IdeenResp>("/api/ideen"),
refetchInterval: relax(refetchInterval),
})
// Ideen-Log (Live Worker Log): pollt nur solange Idee triage/running UND offen
export const useIdeenLog = (id: string, enabled: boolean = false) =>
useQuery({
queryKey: ["ideen-log", id],
queryFn: () => api<IdeenLogResp>(`/api/ideen/${id}/log`),
enabled,
refetchInterval: 4000, // alle ~4s
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, limit],
queryFn: () => api<ChronikResp>(`/api/chronik?limit=${limit}`),
refetchInterval: relax(refetchInterval),
select: (d) => d.items ?? [],
})
export const useWissen = () =>
useQuery({ queryKey: qk.wissen, queryFn: () => api<WissenResp>("/api/wissen") })
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) =>
useQuery({
queryKey: qk.reminders,
queryFn: () => api<{ items: Reminder[] }>("/api/reminders"),
refetchInterval: relax(refetchInterval),
select: (d) => d.items ?? [],
})
// Per-Turn-Latenz-Trace (letzte N Voice/Lucy-Turns mit Stufen-Breakdown).
export const useVoiceTrace = (limit = 12, refetchInterval: number = TAKT.schnell) =>
useQuery({
queryKey: qk.voiceTrace,
queryFn: () => api<VoiceTraceResp>(`/api/voice/trace?limit=${limit}`),
refetchInterval,
select: (d) => d.turns ?? [],
})
export const useMemoryGraph = (enabled = true) =>
useQuery({
queryKey: qk.memoryGraph,
queryFn: () => api<MemoryGraph>("/api/wissen/graph"),
enabled,
staleTime: 60 * 1000,
})
export const useHealth = () =>
useQuery({ queryKey: qk.health, queryFn: () => api<Health>("/api/health"), refetchInterval: TAKT.normal })
export const useSystemStatus = (refetchInterval: number = TAKT.graph) =>
useQuery({ queryKey: qk.systemStatus, queryFn: () => api<SystemStatus>("/api/system/status"), refetchInterval })
// Metrik-Verlauf (Cockpit-Zeitachse): 1h/24h aus dem Backend-Ringpuffer. Nur aktiv,
// wenn eine Karte gerade diesen Bereich zeigt (enabled) — Live bleibt beim 3s-Store.
export const useMetricHistory = (minutes: number, enabled = true) =>
useQuery({
queryKey: ["metrics-history", minutes],
queryFn: () => api<HistoryResp>(`/api/system/history?minutes=${minutes}`),
enabled,
refetchInterval: 60_000,
})
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: relax(refetchInterval) })
export const useGroups = (refetchInterval: number = TAKT.normal) =>
useQuery({ queryKey: qk.groups, queryFn: () => api<GroupsResp>("/api/groups"), refetchInterval })
export const useRouting = (refetchInterval: number = TAKT.normal) =>
useQuery({ queryKey: qk.routing, queryFn: () => api<RoutingResp>("/api/routing"), refetchInterval })
export const useRoutingPolicy = () =>
useQuery({ queryKey: qk.routingPolicy, queryFn: () => api<RoutingPolicyMeta>("/api/routing/policy") })
// Jobs (Downloads) takten etwas schneller, damit Fortschritts-Balken flüssig wirken.
// `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 ?? [],
})
export const useTokenStats = (refetchInterval: number = TAKT.graph) =>
useQuery({ queryKey: qk.tokenStats, queryFn: () => api<TokenStats>("/api/system/token-stats"), refetchInterval })
export const useAgentStatus = (refetchInterval: number = TAKT.normal) =>
useQuery({ queryKey: qk.agentStatus, queryFn: () => api<AgentStatus>("/api/agent/status"), refetchInterval })
// Agent-Hirn (Hermes) + bestes NousResearch-Update. Selten pollen (HF-Abfrage).
export const useHermesBrain = (refetchInterval: number = TAKT.traege) =>
useQuery({ queryKey: qk.hermesBrain, queryFn: () => api<HermesBrainResp>("/api/agent/brain"), refetchInterval })
// Updates-Check ist teuer (apt/git auf der Box) — träge pollen reicht völlig.
export const useUpdates = (refetchInterval: number = TAKT.traege) =>
useQuery({ queryKey: qk.updates, queryFn: () => api<UpdatesResp>("/api/maintenance/updates"), refetchInterval })
export const useDiscover = () =>
useQuery({ queryKey: qk.discover, queryFn: () => api<DiscoverResp>("/api/discover") })
// Verfügbare Spec-Draft-Modelle + Vocab-Kompatibilität zum Ziel-Modell (GGUF-Pfad).
export const useDrafts = (target?: string) =>
useQuery({
queryKey: qk.drafts(target),
queryFn: () => api<DraftsResp>(`/api/models/drafts?target=${encodeURIComponent(target ?? "")}`),
enabled: !!target,
})
export const useConnect = (params?: string) =>
useQuery({
queryKey: qk.connect(params),
queryFn: () => api<ConnectResp>(params ? `/api/connect?${params}` : "/api/connect"),
})
// Live-Status der zwei Leitungen (Gateway + Gedächtnis) — alle 15s aktualisiert.
export const useConnectHealth = () =>
useQuery({
queryKey: qk.connectHealth,
queryFn: () => api<ConnectHealth>("/api/connect/health"),
refetchInterval: 15000,
})
/** Nach Mutationen die betroffenen Listen-Queries neu ziehen. */
export function invalidate(qc: QueryClient, ...keys: (readonly unknown[])[]) {
for (const key of keys) qc.invalidateQueries({ queryKey: key })
}
export { useQueryClient }