1570b2fc71
User-Auftrag 12.07. ("bau das alles ein im Sinne der Autonomie. Auch muss
Lucy selbst bemerken wenn sich etwas aendert"):
- deploy/selbst-inventur.sh (Cron 03:35): SELBST-STECKBRIEF wird LIVE aus
der Realitaet GENERIERT (Versionen, Dienste, Timer, Modelle, Crons,
Queue, Karten -> ~/.hermes/state/selbst-steckbrief.md) statt von Hand
gepflegt. Diff gegen gestern => die Box BEMERKT SELBST Aenderungen an
sich (stille Chronik-Karte + kurze Telegram-Zeile; sonst still).
- deploy/karten-gutachter.sh (Cron 04:00): stempelt jede neue Karte mit
"Empfehlung: ANNEHMEN/ABLEHNEN/UNKLAR + ein ehrlicher Satz" (prueft
Redundanz gegen den Steckbrief, Risiko, Nutzen; Ein-Schuss-Richter
gpt-oss/GLM; Stempel = Meinung, kein Gate; neuer Commit entwertet den
alten Stempel). Backend mischt den Stempel in /api/auftragsbuch,
UI zeigt ihn farbig auf der Karte.
- Ablehnen mit Grund (Lern-Klick): UI-Inline-Feld beim Ablehnen ->
/srv/models/mc2-ablehnungen.jsonl -> Idle-Radar bekommt die Gruende
als "NIE wieder vorschlagen"-Material vorgelegt.
- idle-radar-feed.sh: Material + Raster erweitert um Selbst-Steckbrief,
Inventur-Diff und Ablehn-Gruende (Fehlerfall 12.07.: redundanter
Gateway-Restart-Vorschlag waere damit gestorben).
- werkstatt-SOUL Regel 7 + wartung-Skill: REALITAETS-CHECK - existiert es
schon? Dann kanban_block statt Branch (ein Branch fuer Ueberfluessiges
ist ein gescheiterter Lauf).
- hermes-release-radar-feed.sh: echter Treffer legt zusaetzlich EINE rohe
Idee in die Queue (idempotent je Release-Tag) - der Blick nach draussen
fuettert denselben Kreislauf.
- deploy.sh: kopiert beide neuen Skripte; Doku AUFTRAGSBUCH.md +
wissen/OFFENE-FAEDEN.md; frontend/dist frisch gebaut (tsc+vite gruen,
UI gegen Live-Box verifiziert).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
586 lines
16 KiB
TypeScript
586 lines
16 KiB
TypeScript
// Schmaler Fetch-Helfer gegen das MC-2-Backend (/api/*).
|
|
|
|
export async function api<T = unknown>(path: string, init?: RequestInit): Promise<T> {
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
...init?.headers,
|
|
} as Record<string, string>
|
|
|
|
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<string, any> = {}
|
|
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<T>
|
|
}
|
|
|
|
// 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<string, GroupSpec>
|
|
}
|
|
export const getGroups = () => api<GroupsResp>("/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<string, string[]>[]
|
|
context_window_fallbacks: Record<string, string[]>[]
|
|
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<RoutingPolicyMeta>("/api/routing/policy")
|
|
export const updateRoutingPolicy = (patch: Partial<RoutingPolicy>) =>
|
|
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<string, ConnectTool> // 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<string, { in: number; out: number }>
|
|
}
|
|
|
|
// 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
|
|
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
|
|
}
|