// Schmaler Fetch-Helfer gegen das MC-2-Backend (/api/*). export async function api(path: string, init?: RequestInit): Promise { const headers = { "Content-Type": "application/json", ...init?.headers, } as Record const sudoPassword = localStorage.getItem("mc_sudo_password") const hfToken = localStorage.getItem("mc_hf_token") if (sudoPassword) { headers["X-Sudo-Password"] = sudoPassword } let body = init?.body const method = init?.method?.toUpperCase() || "GET" if (method === "POST") { if (typeof body === "string") { try { const data = JSON.parse(body) let changed = false if (sudoPassword && !("sudo_password" in data)) { data["sudo_password"] = sudoPassword changed = true } if (hfToken && !("hf_token" in data)) { data["hf_token"] = hfToken changed = true } if (changed) { body = JSON.stringify(data) } } catch (e) { // ignore JSON parse errors } } else if (!body) { const data: Record = {} if (sudoPassword) data["sudo_password"] = sudoPassword if (hfToken) data["hf_token"] = hfToken if (Object.keys(data).length > 0) { body = JSON.stringify(data) } } } const res = await fetch(path, { ...init, headers, body, }) if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) return res.json() as Promise } // llama-swap-`groups`: Mitglieder mit swap=false dürfen GLEICHZEITIG resident sein (Ko-Residenz). // Die `brains`-Gruppe hält Hirn (fast) + Augen (vision) gemeinsam warm, statt sich zu verdrängen. export interface GroupSpec { swap: boolean persist: boolean members: string[] } export interface GroupsResp { groups: Record } export const getGroups = () => api("/api/groups") export const setGroup = (group: string, members: string[], swap = false, persist = true) => api("/api/groups", { method: "PUT", body: JSON.stringify({ group, members, swap, persist }) }) export interface Capabilities { moe: boolean active_b: number | null tools: "yes" | "likely" | "no" vision: boolean coder: boolean reasoning: boolean embedding: boolean ctx: number | null params_b: number | null arch: string | null } export interface Fit { level: "perfect" | "marginal" | "too_tight" text: string req_gb: number tps: number } export interface RoleRecModel { name: string current_role: string | null params_b: number quant: string fit: Fit suitable: boolean incomplete: boolean score: number reason: string recommended: boolean } export interface RoleRecResp { role: string recommended: string | null models: RoleRecModel[] } export interface FitResp { params_b: number fit: Fit optimal_ctx: number assigned_ctx: number budget: { gtt_gb: number; reserved_gb: number; budget_gb: number; mode: string } sys_ram_gb: number } export interface ModelInfo { name: string role: string | null aliases: string[] api_ids: string[] ctx: number | null ttl: number | null cmd: string gguf_path: string filename: string quant: string size_bytes: number | null incomplete: boolean prompt_cache: boolean spec_draft_model: string | null spec_type: string | null spec_active: boolean parallel_slots: number capabilities: Capabilities } export interface VocabFingerprint { model: string | null pre: string | null n_vocab: number | null arch: string | null } export interface DraftInfo { path: string filename: string size_bytes: number | null vocab: VocabFingerprint | null compatible: boolean | null // null = nicht prüfbar (Ziel-GGUF fehlt) mtp?: boolean // MTP-Kopf (Multi-Token-Prediction) statt klassischem Draft } export interface DraftsResp { target_path: string target_exists: boolean target_vocab: VocabFingerprint | null drafts: DraftInfo[] } export interface DiscoverModel { name: string author: string repo: string role: string params_b: number quant: string tags: string[] downloads: number fit: Fit optimal_ctx: number caps: Capabilities } export interface DiscoverCategory { role: string title: string icon: string models: DiscoverModel[] recommended: string | null } export interface DiscoverResp { updated: number categories: DiscoverCategory[] sys_ram_gb: number } export interface RoutingResp { mode?: string endpoint?: string heavy_threshold_chars?: number routes: { name: string; target: string }[] lanes?: { name: string; target: string; threshold_chars?: number; escalate_chars?: number; aka?: string }[] fallbacks: Record[] context_window_fallbacks: Record[] gateway_reachable: boolean } // UI-editierbare Routing-Policy (GET/PUT /api/routing/policy). Aliase + Zeichen-Schwellen // hinter den Lanes; hot-reload im Backend (kein Restart). export interface RoutingPolicy { fast: string heavy: string coder: string coder_lite: string heavy_chars: number coding_escalate_chars: number fast_no_think: boolean } export interface RoutingPolicyField { key: keyof RoutingPolicy label: string type: "str" | "int" | "bool" min?: number max?: number } export interface RoutingPolicyMeta { policy: RoutingPolicy defaults: RoutingPolicy fields: RoutingPolicyField[] } export const getRoutingPolicy = () => api("/api/routing/policy") export const updateRoutingPolicy = (patch: Partial) => api<{ policy: RoutingPolicy }>("/api/routing/policy", { method: "PUT", body: JSON.stringify(patch) }) export interface GitInfo { hash: string date: string subject: string branch: string dirty: boolean path: string } // Engine kann git-, binary- oder unbekannte Version sein — Felder je nach `type`. export interface ComponentVersion { type: "git" | "binary" | "unknown" hash?: string date?: string subject?: string branch?: string dirty?: boolean path?: string version_text?: string } export interface Versions { mc2: GitInfo | null engine: ComponentVersion hermes_agent: GitInfo | null } export interface SystemStatus { cpu: { percent: number; cores: number | null } ram: { total: number; used: number; percent: number } gpu: { busy_percent: number | null vram_used: number | null vram_total: number | null gtt_used?: number | null gtt_total?: number | null } | null temp: { cpu?: number; gpu?: number } | null disk: { total: number; used: number; percent: number } | null versions?: Versions } export interface ServicesResp { services: { name: string; unit: string; url: string; ok: boolean }[] links: { engine_ui: string; gateway: string; hermes_ui: string } } export interface ComponentUpdate { key: string // z.B. "hermes_agent" name: string current: string | null latest: string | null update: boolean | null // null = unbestimmbar (installierte Version remote nicht abfragbar) reachable: boolean | null } export interface UpdatesResp { os: number engine: number swap: number models: number model_list: { role: string; title: string; repo: string }[] last_check?: number | null components?: ComponentUpdate[] } export interface UpdateDetails { kind: "os" | "engine" | "swap" | "hermes" error?: string // LLM-Zusammenfassung in Lucys Stimme (hermes/engine/swap; Breaking Changes zuerst) summary?: string // Aktions-Verdikt: muss der Besitzer selbst etwas tun? (null = kein Verdikt/keine Summary) action_needed?: boolean | null action_text?: string // os count?: number packages?: { name: string; current: string; candidate: string }[] // os: vom System zurückgestellte Pakete (Phasen-Rollout / kept back) — ehrlich statt "hängt" held_back?: { name: string; reason: "phasing" | "kept_back" }[] // engine installed_build?: number | null latest_build?: number | null latest_tag?: string | null name?: string | null url?: string | null body?: string | null // hermes branch?: string | null behind?: number commits?: { hash: string; subject: string; when: string }[] } export interface ConnectTool { label: string lang: string snippet: string note: string } export interface ConnectResp { host: string gateway_url: string mc_url: string tools: Record // Leitung 1 — Modell (IDEs/Agenten → Gateway) memory: ConnectTool // Leitung 2 — Gedächtnis (separater MCP-Server) } export interface ConnectLine { ok: boolean detail: string } export interface ConnectHealth { gateway: ConnectLine memory: ConnectLine desktop_gateway: ConnectLine } export interface Memory { id: string content: string category: string source: string created_at: string updated_at: string score?: number // Relevanz bei semantischer Suche (q gesetzt); sonst undefined } export interface MemoryGraphNode { id: string content: string category: string source: string } export interface MemoryGraph { nodes: MemoryGraphNode[] edges: { source: string; target: string; weight: number }[] } export interface DedupeResult { groups: { keep: { id: string; content: string; category: string }; remove: { id: string; content: string }[] }[] duplicate_count: number removed: number applied: boolean } export interface AgentStatus { gateway_url: string box_console_url?: string hermes_ui_url?: string gateway_reachable: boolean box_console_reachable?: boolean hermes_ui_reachable?: boolean home_exists: boolean brain_model?: string has_config: boolean has_skills: boolean has_memories: boolean telegram_enabled?: boolean mcp_server_count?: number pc_executor_reachable?: boolean } export interface Job { id: string label: string group?: string | null // "maintenance" = system-veränderndes Update (Wartungs-Riegel) state: "queued" | "running" | "done" | "failed" | "canceled" progress?: number total_bytes?: number done_bytes?: number rate_bps?: number eta_s?: number } export interface Health { status: string version: string engine_reachable: boolean gateway_reachable: boolean brain?: { role: string; model: string | null; ready: boolean } } export interface TokenStats { prompt_tokens: number completion_tokens: number total_tokens: number saved_usd: number saved_eur: number pricing?: Record } // Per-Turn-Latenz-Trace (GET /api/voice/trace). Balken-Stufen (zeitlich disjunkt): // stt · vision · hirn · gen. mem0 = Unter-Detail INNERHALB von hirn (nicht zum Balken addieren). export interface VoiceTurn { id: string ts: number // Epoch-Sekunden session: string kind: string had_images: boolean stt_ms: number | null vision_ms: number | null hirn_ms: number | null // Zeit bis erstes Inhalts-Token (Agent + Mem0 + LLM-TTFT) gen_ms: number | null // Generierung nach dem ersten Token bis Stream-Ende mem0_ms: number | null // Teil VON hirn (Mem0-Retrieve), best-effort total_ms: number error: string | null } export interface VoiceTraceResp { turns: VoiceTurn[] } export interface ModelsResp { models: ModelInfo[] running?: string[] } // ── Auftragsbuch (Vorschlags-Inbox) ────────────────────────────────────────── export interface AuftragStatus { state: "laeuft" | "eingespielt" | "fehlgeschlagen" | "rollback" | "zurueckgerollt" | "kritisch" | "abgelehnt" detail: string ts: number } export interface AuftragItem { repo?: "mc2" | "lucy" // Quelle: Box-Stack (mc2) oder Lucy-Desktop (Annahme baut am PC) branch: string kind: string // wartung | orchestrator | doku | feature subject: string body: string author: string ts: number | null ahead: number behind: number shortstat: string files: { status: string; path: string }[] files_truncated: boolean frontend_ohne_build: boolean // Frontend-Quelltext ohne dist → Box kann nicht bauen verwaist?: boolean // kein gemeinsamer Ursprung mit main (kaputt aufgesetzt) → Merge unmöglich leer?: boolean // Diff gegen main ist leer → bringt nichts (schon in main / am Repo vorbei) status: AuftragStatus | null // Empfehlungs-Stempel des nächtlichen Karten-Gutachters (Meinung, kein Gate). empfehlung: { empfehlung: "ANNEHMEN" | "ABLEHNEN" | "UNKLAR"; satz: string; richter: string } | null } export interface SkillKandidat { file: string title: string preview: string mtime: number } export interface AuftragsbuchResp { available: boolean items: AuftragItem[] skill_kandidaten: SkillKandidat[] open_count: number } // ── Ideen-Queue (natives Hermes-Kanban) ────────────────────────────────────── export interface IdeenItem { id: string titel: string body: string status: string // triage | todo | ready | running | blocked | review | done assignee: string | null erstellt: number | null fertig: number | null von: string | null } export interface IdeenResp { available: boolean items: IdeenItem[] offen: number fehler?: string } // ── Chronik (Timeline aus dem Melde-Briefkasten) ───────────────────────────── export interface ChronikItem { id: number ts: number subject: string text: string source: string priority: "normal" | "silent" } export interface ChronikResp { items: ChronikItem[] } // ── Wissens-Vault (Traum-Notizen, read-only) ───────────────────────────────── export interface WissenFile { path: string name: string dir: string title: string mtime: number neu: boolean } export interface WissenResp { available: boolean files: WissenFile[] } export interface WissenDatei { path: string content: string mtime: number } // ── Zeitmaschine (Snapshots + Restore) ─────────────────────────────────────── export interface Snapshot { snapshot: string file: string size_mb: number } export interface ZeitmaschineResp { available: boolean backups: Snapshot[] } // ── Erinnerungen (Wecker/Routinen) ─────────────────────────────────────────── export interface Reminder { id: number text: string next_ts: number repeat: string // '' einmalig | daily | weekdays | weekly created_ts: number } export interface HermesBrainModel { name: string filename?: string params_b: number | null quant?: string size_bytes?: number | null version?: number | null gguf_path?: string incomplete?: boolean } export interface HermesBrainCandidate { repo: string name: string version: number params_b: number downloads: number fit: Fit } export interface HermesBrainBudget { gtt_gb: number brain_gb: number // Footprint des (empfohlenen) Brains, das immer resident bleibt warm_projected_gb: number // alle persist-Modelle warm (Info) free_after_gb: number // frei nach Brain + größtem on-demand largest_ondemand_gb: number // größtes on-demand-Modell (z.B. heavy/coder) fits: boolean // passt Brain + größtes on-demand zusammen ins Budget? } export interface HermesBrainResp { current: HermesBrainModel | null recommended: HermesBrainCandidate | null update_available: boolean budget?: HermesBrainBudget | null }