4 Commits

Author SHA1 Message Date
Hitonabi ccc9a25a25 UMBAU v3 P1: Gateway-Auszug — /v1 wird eigener Prozess mc2-gateway (:9010)
ACHTUNG: Annahme AKTIVIERT die Stufe 1 (deploy.sh installiert
mc2-gateway.service + setzt MC_V1_UPSTREAM in der MC2-Unit; Health mit
Kaltstart-Retry bis 12s, dann hart rot -> Runner-Rollback greift).
MC2 :9001/v1 wird duenner Roh-Weiterleiter, LAN-Clients merken nichts;
Rollback = MC_V1_UPSTREAM-Zeile aus der Unit entfernen. token_stats
laedt bei Fremd-Aenderung per mtime nach (Gateway schreibt, Steuerpult
liest). UMBAUPLAN Abschnitt 3b dokumentiert P1-P4. Stufe 2 (Lucy direkt
an :9010, ueberlebt MC2-Neustarts) = deploy/gateway-cutover.sh, separat.

Neu aufgesetzt 15.07. auf aktuellem main (a3d9c74): die urspruengliche
Karte trug die inzwischen veraltete Von-allein-View doppelt - die ist
laengst auf main live. Inhalt = P1 der Parallel-Session, unveraendert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:51:51 +02:00
Hitonabi a3d9c745cd Von allein: Punkt-Ordner (.archive) nicht als Skills listen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:49:15 +02:00
Hitonabi b0eec715c8 Von allein: Kategorie-Skills (2. Ebene) mitzeigen (research/blogwatcher & Co.)
Die mitgelieferten Hermes-Skills sind Kategorie-Ordner (DESCRIPTION.md +
Unter-Skills mit eigener SKILL.md) - der Walker lief nur ueber Ebene 1
und zeigte 12 statt ~40 Skills. Jetzt beide Ebenen, Unter-Skills mit
Kategorie-Badge; Detail-Endpunkt erlaubt Kategorie/Name-Pfad.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:47:53 +02:00
Hitonabi 86bd72838a Neue Ansicht 'Von allein': Skills + Vorschlags-Bilanz der Box (+ schlafende P1-Basis)
User-Wunsch 15.07.: EIN Viewpoint, was die Box selbst geleistet hat -
Chronik ist zu kompliziert. Neue Operativ-Ansicht #eigenleben:
- Skills aus ~/.hermes/skills (= Lucys Satz) mit Klartext-Beschreibung,
  Nutzungszaehler (.usage.json) und Herkunft (selbst erstellt / von uns
  gebaut / mitgeliefert), aufklappbar mit vollem SKILL.md.
- Vorschlags-Bilanz: angenommen (Auftragsbuch-Chronik) + abgelehnt mit
  Grund (Lern-Journal) als eine Timeline + Zaehler.
Backend: services/eigenleben.py + routers/eigenleben.py (read-only).

Mit an Bord (User-Ja, SCHLAFEND): config.V1_UPSTREAM + app.py-Weiche +
gateway_forward aus dem P1-Gateway-Auszug - ohne MC_V1_UPSTREAM in der
Unit exakt altes Verhalten. Der aktive Rest von P1 kommt separat ueber
Branch umbau/p1-gateway-auszug (dort liegt auch diese View schon).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:45:04 +02:00
151 changed files with 908 additions and 123 deletions
+12 -2
View File
@@ -19,13 +19,14 @@ from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from starlette.requests import Request
from config import FRONTEND_DIST, VERSION
from config import FRONTEND_DIST, V1_UPSTREAM, VERSION
from routers import (
agent,
auftragsbuch,
chronik,
connect,
console,
eigenleben,
gateway_proxy,
health,
hermes_ui,
@@ -121,11 +122,20 @@ app.include_router(
app.include_router(
reminders_router.router
) # Erinnerungen/Routinen (A3) — feuern in den Briefkasten
app.include_router(gateway_proxy.router) # OpenAI-kompatibler /v1-Gateway (model:auto)
# /v1-Datenpfad: Nach dem Gateway-Auszug (UMBAU v3 P1) läuft der eigentliche Gateway als
# eigener Prozess (mc2-gateway, Loopback :9010) — MC2 reicht /v1 dann nur roh durch, damit
# LAN-Clients (IDE-Lane) weiter über :9001 kommen. Ohne MC_V1_UPSTREAM (vor dem ersten
# Deploy der neuen Unit / nach Rollback) bedient MC2 /v1 wie bisher selbst.
if V1_UPSTREAM:
from routers import gateway_forward
app.include_router(gateway_forward.router) # dünner Roh-Weiterleiter → mc2-gateway
else:
app.include_router(gateway_proxy.router) # OpenAI-kompatibler /v1-Gateway (model:auto)
app.include_router(maintenance.router)
app.include_router(auftragsbuch.router) # Vorschlags-Inbox (Mensch-Gate als Klick)
app.include_router(ideen.router) # Ideen-Queue (natives Hermes-Kanban) — Tür der Zentrale
app.include_router(chronik.router) # Timeline der autonomen Taten (Announce-Store)
app.include_router(eigenleben.router) # „Von allein": Skills + Vorschlags-Bilanz der Box
app.include_router(wissen.router) # Wissens-Vault (Traum-Notizen) read-only
app.include_router(zeitmaschine.router) # Snapshots ansehen + Ein-Klick-Restore (detached)
app.include_router(
+5
View File
@@ -61,6 +61,11 @@ HF_DOWNLOAD_ENV = {"HF_HUB_DISABLE_XET": "1"}
# MC2 IST der Gateway (services/gateway.py + routers/gateway_proxy.py). KEIN externer
# LiteLLM-Dienst (scheitert auf Python 3.14). Daher keine Gateway-Config-Datei mehr.
GATEWAY_URL = os.environ.get("MC_GATEWAY_URL", f"http://127.0.0.1:{os.environ.get('MC_PORT', '9000')}").rstrip("/")
# Gateway-Auszug (UMBAU v3 P1): Ist MC_V1_UPSTREAM gesetzt (Unit-Env, z. B.
# http://127.0.0.1:9010), bedient der eigenständige mc2-gateway-Prozess den /v1-Pfad
# und MC2 reicht /v1 nur noch roh durch (routers/gateway_forward.py). Leer = altes
# Verhalten, MC2 bedient /v1 selbst — die Zeile aus der Unit nehmen ist der Rollback.
V1_UPSTREAM = os.environ.get("MC_V1_UPSTREAM", "").rstrip("/")
# --- Hermes Agent (eigener Dienst auf der Box) -------------------------------
# Gateway (OpenAI-API des Agenten) + interaktives Web-Terminal (ttyd → `hermes chat`).
+64
View File
@@ -0,0 +1,64 @@
"""
MC2-Gateway — der /v1-Datenpfad als EIGENER Prozess (UMBAU v3, P1).
Befund der Live-Inspektion 15.07.2026: Jeder LLM-Aufruf der Box (Lucys Haupt-Hirn,
Nacht-Crons, Worker-Delegation, Vision, Decomposer/Specifier/Curator) lief durch den
Steuerpult-Prozess auf :9001 — den am häufigsten neu gestarteten Dienst des Stacks.
Dieser Einstieg hebt denselben Gateway-Router (routers/gateway_proxy.py) UNVERÄNDERT
in einen bewusst winzigen, langweiligen Prozess: Unit mc2-gateway.service, Loopback
:9010, Restart=always. Das Steuerpult darf beliebig neu starten — die Wirbelsäule steht.
Bewusst NICHT hier: weitere Router, Hintergrund-Loops, CORS, Frontend-Auslieferung.
LAN-Clients (IDE-Lane) erreichen /v1 weiter über MC2 :9001, das roh hierher
durchreicht (routers/gateway_forward.py, MC_V1_UPSTREAM).
"""
import logging
import os
from contextlib import asynccontextmanager
from typing import AsyncGenerator
import httpx
from fastapi import FastAPI, Request
from config import LLAMA_SWAP_URL, VERSION
from routers import gateway_proxy
logging.basicConfig(
level=os.environ.get("MC_LOG_LEVEL", "INFO").upper(),
format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
)
log = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
# Gleiche Client-Parameter wie zuvor in app.py: Keep-Alive/Pooling statt neuer
# Client pro Anfrage (Sockets/TIME_WAIT unter parallelen Agent-Strömen).
app.state.gw_client = httpx.AsyncClient(
timeout=httpx.Timeout(connect=10.0, read=None, write=None, pool=10.0),
limits=httpx.Limits(max_keepalive_connections=100, max_connections=200),
)
log.info("mc2-gateway bereit (Engine: %s)", LLAMA_SWAP_URL)
try:
yield
finally:
await app.state.gw_client.aclose()
app = FastAPI(title="MC2 Gateway", version=VERSION, lifespan=lifespan)
app.include_router(gateway_proxy.router)
@app.get("/gw/health")
async def health(request: Request):
"""Eigener Health-Pfad (nicht /api/health — das gehört dem Steuerpult):
beweist Prozess UND Engine-Erreichbarkeit, für deploy.sh/stack-postcheck.sh."""
engine = False
try:
r = await request.app.state.gw_client.get(f"{LLAMA_SWAP_URL}/v1/models", timeout=5.0)
engine = r.status_code == 200
except httpx.HTTPError:
pass
return {"status": "ok", "service": "mc2-gateway", "version": VERSION,
"engine_reachable": engine}
+20
View File
@@ -0,0 +1,20 @@
"""Eigenleben-Endpoints — „Von allein"-Ansicht (Skills + Vorschlags-Bilanz), read-only."""
from fastapi import APIRouter, HTTPException
from services import eigenleben
router = APIRouter(prefix="/api")
@router.get("/eigenleben")
def get_overview() -> dict:
return eigenleben.overview()
@router.get("/eigenleben/skill/{skill_id}")
def get_skill(skill_id: str) -> dict:
text = eigenleben.skill_text(skill_id)
if text is None:
raise HTTPException(404, "Skill nicht gefunden.")
return {"id": skill_id, "text": text}
+67
View File
@@ -0,0 +1,67 @@
"""
Dünner /v1-Roh-Weiterleiter (UMBAU v3, P1).
Wenn MC_V1_UPSTREAM gesetzt ist (Unit-Env), reicht das Steuerpult /v1 unangefasst an
den eigenständigen mc2-gateway-Prozess (Loopback :9010) durch — LAN-Clients wie die
IDE-Lane erreichen den Gateway sonst nicht. Hier passiert BEWUSST nichts: kein
Routing, keine Bild-Weiche, keine Token-Zählung — all das macht genau einmal der
Gateway-Prozess (routers/gateway_proxy.py). Rollback = Env-Zeile aus der Unit
entfernen → app.py bindet wieder den lokalen Gateway ein.
"""
import logging
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse, StreamingResponse
from config import V1_UPSTREAM
log = logging.getLogger(__name__)
router = APIRouter(prefix="/v1")
# Hop-by-hop-Header dürfen nicht blind weitergereicht werden; content-length wird von
# httpx (Request) bzw. Starlette-Chunking (Response) neu bestimmt.
_HOP_HEADERS = {
"host", "content-length", "connection", "keep-alive", "transfer-encoding",
"upgrade", "proxy-authenticate", "proxy-authorization", "te", "trailer",
}
def _clean(headers) -> dict:
return {k: v for k, v in headers.items() if k.lower() not in _HOP_HEADERS}
@router.api_route("/{path:path}", methods=["GET", "POST"])
async def forward(path: str, request: Request):
client = request.app.state.gw_client # geteilter Keep-Alive-Client (app.py lifespan)
url = f"{V1_UPSTREAM}/v1/{path}"
if request.url.query:
url = f"{url}?{request.url.query}"
body = await request.body()
req = client.build_request(
request.method, url, content=body or None, headers=_clean(request.headers),
timeout=None,
)
try:
upstream = await client.send(req, stream=True)
except Exception as exc: # Gateway-Prozess weg → ehrlicher 502 statt Hänger
log.warning("/v1-Weiterleitung an %s fehlgeschlagen: %s", V1_UPSTREAM, exc)
return JSONResponse(
{"error": {"message": f"mc2-gateway ({V1_UPSTREAM}) nicht erreichbar: {exc}",
"type": "gateway_unavailable"}},
status_code=502,
)
async def gen():
try:
async for chunk in upstream.aiter_raw():
yield chunk
finally:
await upstream.aclose()
# Streaming-Passthrough für BEIDE Fälle (SSE + normale JSON-Antwort): Starlette
# chunkt selbst, Status/Header (inkl. x-mc-routed-to) kommen vom Gateway.
return StreamingResponse(
gen(), status_code=upstream.status_code, headers=_clean(upstream.headers)
)
+177
View File
@@ -0,0 +1,177 @@
"""Eigenleben — was die Box von allein gebaut und vorgeschlagen hat.
Beantwortet die User-Frage vom 15.07.2026: „Es gibt keinen Viewpoint, welche Skills
erstellt wurden, was die Box vorgeschlagen hat, welche Skills Lucy hat und wie die
funktionieren." Reine LESE-Schicht, keine Schreib-Operationen:
- Skills aus ~/.hermes/skills (= Lucys Skill-Satz; die Worker-Profile werkstatt/betrieb
tragen Kopien desselben Satzes). Herkunft dreistufig klassifiziert:
selbst → weder mitgeliefert noch aus unserem Repo = die Box/Lucy hat ihn erzeugt
repo → liegt in ~/mission-control-v2/deploy/skills (von uns gebaut + deployt)
bundled → liegt in ~/.hermes/hermes-agent/skills (kam mit Hermes mit)
- Nutzungszahlen aus ~/.hermes/skills/.usage.json (Hermes' eigener Zähler).
- Bilanz aus Auftragsbuch-Chronik (/srv/models/mc2-auftragsbuch.json, angenommene
Branches) + Ablehnungs-Lern-Journal (/srv/models/mc2-ablehnungen.jsonl).
"""
from __future__ import annotations
import json
import re
from pathlib import Path
SKILLS_DIR = Path.home() / ".hermes" / "skills"
BUNDLED_DIR = Path.home() / ".hermes" / "hermes-agent" / "skills"
REPO_DIR = Path.home() / "mission-control-v2" / "deploy" / "skills"
USAGE_FILE = SKILLS_DIR / ".usage.json"
AUFTRAGSBUCH_FILE = Path("/srv/models/mc2-auftragsbuch.json")
ABLEHNUNGEN_FILE = Path("/srv/models/mc2-ablehnungen.jsonl")
_HERKUNFT_LABEL = {
"selbst": "Selbst erstellt",
"repo": "Von uns gebaut",
"bundled": "Mit Hermes mitgeliefert",
}
def _frontmatter(text: str) -> dict:
"""Sehr kleiner Frontmatter-Leser: nur die flachen key: value-Zeilen des ersten
---Blocks (name/description/version/author reichen hier; kein YAML-Import nötig)."""
out: dict = {}
if not text.startswith("---"):
return out
for line in text.split("\n", 1)[1].split("\n"):
if line.strip() == "---":
break
m = re.match(r"^([A-Za-z_][\w-]*):\s*(.*)$", line)
if not m:
continue
val = m.group(2).strip().strip('"').strip("'")
if val:
out[m.group(1)] = val
return out
def _norm(s: str) -> str:
return re.sub(r"[\s_-]+", "", (s or "").lower())
def list_skills() -> list[dict]:
"""Alle Skills mit Beschreibung, Herkunft und Nutzungszahlen (leer im Dev-Modus)."""
if not SKILLS_DIR.is_dir():
return []
try:
usage_raw = json.loads(USAGE_FILE.read_text(encoding="utf-8"))
except Exception:
usage_raw = {}
usage = {_norm(k): v for k, v in usage_raw.items()}
def eintrag(md: Path, rel: str, kategorie: str | None) -> dict:
try:
fm = _frontmatter(md.read_text(encoding="utf-8", errors="replace"))
except Exception:
fm = {}
if (BUNDLED_DIR / rel).is_dir():
herkunft = "bundled"
elif (REPO_DIR / rel).is_dir():
herkunft = "repo"
else:
herkunft = "selbst"
kurz = rel.split("/")[-1]
u = usage.get(_norm(kurz)) or usage.get(_norm(fm.get("name", ""))) or {}
return {
"id": rel,
"name": fm.get("name") or kurz,
"kategorie": kategorie,
"beschreibung": fm.get("description") or "",
"version": fm.get("version") or "",
"autor": fm.get("author") or "",
"herkunft": herkunft,
"herkunft_label": _HERKUNFT_LABEL[herkunft],
"genutzt": int(u.get("use_count") or 0),
"zuletzt_genutzt": u.get("last_used_at") or None,
"erstellt": u.get("created_at") or None,
"status": u.get("state") or "active",
"geaendert": md.stat().st_mtime,
}
skills: list[dict] = []
for d in sorted(SKILLS_DIR.iterdir()):
if not d.is_dir() or d.name.startswith("."):
continue # .archive (Curator) & Co. sind keine aktiven Skills
md = d / "SKILL.md"
if md.is_file():
skills.append(eintrag(md, d.name, None))
continue
# Kategorie-Ordner (mitgelieferte Sammlungen wie research/, apple/): eine Ebene
# tiefer liegen die echten Skills (research/blogwatcher/SKILL.md) — je einzeln zeigen.
for sub in sorted(d.iterdir()):
smd = sub / "SKILL.md"
if sub.is_dir() and not sub.name.startswith(".") and smd.is_file():
skills.append(eintrag(smd, f"{d.name}/{sub.name}", d.name))
# Selbst erstellte zuerst — das ist der Star der Ansicht.
order = {"selbst": 0, "repo": 1, "bundled": 2}
skills.sort(key=lambda s: (order[s["herkunft"]], -s["genutzt"], s["name"]))
return skills
def skill_text(skill_id: str) -> str | None:
"""Voller SKILL.md-Text („wie funktioniert der Skill") — max. Kategorie/Name, keine Pfad-Tricks."""
if not re.fullmatch(r"[\w.-]+(/[\w.-]+)?", skill_id or ""):
return None
md = SKILLS_DIR / skill_id / "SKILL.md"
if not md.is_file():
return None
try:
return md.read_text(encoding="utf-8", errors="replace")
except Exception:
return None
def bilanz() -> dict:
"""Vorschlags-Bilanz: was die Box vorschlug und was daraus wurde (eine Timeline)."""
eintraege: list[dict] = []
try:
branches = json.loads(AUFTRAGSBUCH_FILE.read_text(encoding="utf-8")).get("branches") or {}
for branch, info in branches.items():
eintraege.append({
"art": "angenommen" if info.get("state") == "eingespielt" else info.get("state", "?"),
"titel": branch,
"detail": info.get("detail") or "",
"ts": float(info.get("ts") or 0),
})
except Exception:
pass
try:
for line in ABLEHNUNGEN_FILE.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
try:
j = json.loads(line)
except Exception:
continue
eintraege.append({
"art": "abgelehnt",
"titel": j.get("subject") or j.get("branch") or "?",
"detail": j.get("grund") or "",
"ts": float(j.get("ts") or 0),
})
except Exception:
pass
eintraege.sort(key=lambda e: -e["ts"])
return {
"eintraege": eintraege,
"angenommen": sum(1 for e in eintraege if e["art"] == "angenommen"),
"abgelehnt": sum(1 for e in eintraege if e["art"] == "abgelehnt"),
}
def overview() -> dict:
skills = list_skills()
b = bilanz()
return {
"available": SKILLS_DIR.is_dir(),
"skills": skills,
"selbst_erstellt": sum(1 for s in skills if s["herkunft"] == "selbst"),
"bilanz": b,
}
+26 -1
View File
@@ -26,9 +26,22 @@ _lock = threading.Lock()
_stats: dict | None = None
_dirty = False
_last_flush = 0.0
# mtime der Datei beim letzten eigenen Laden/Schreiben — seit dem Gateway-Auszug
# (UMBAU v3 P1) schreibt der mc2-gateway-Prozess die Datei, das Steuerpult liest nur
# noch: ohne mtime-Vergleich zeigte es ab Prozessstart eingefrorene Zahlen.
_disk_mtime: float | None = None
def _stat_mtime() -> float | None:
try:
return STATS_FILE.stat().st_mtime
except OSError:
return None
def _load_from_disk() -> dict:
global _disk_mtime
_disk_mtime = _stat_mtime()
if not STATS_FILE.exists():
return dict(_BASELINE)
try:
@@ -51,19 +64,31 @@ def _ensure_loaded() -> dict:
def _write(stats: dict) -> None:
global _disk_mtime
try:
STATS_FILE.parent.mkdir(parents=True, exist_ok=True)
tmp = STATS_FILE.with_suffix(".tmp")
with open(tmp, "w", encoding="utf-8") as f:
json.dump(stats, f)
tmp.replace(STATS_FILE)
_disk_mtime = _stat_mtime() # eigener Write ist kein Fremd-Update
except OSError:
log.warning("token_stats: Schreiben fehlgeschlagen", exc_info=True)
def get_stats() -> dict:
"""Aktueller Stand (inkl. noch nicht geflushter Inkremente) als Kopie."""
"""Aktueller Stand (inkl. noch nicht geflushter Inkremente) als Kopie.
Multi-Prozess-fähig: Hat ein ANDERER Prozess (mc2-gateway) die Datei inzwischen
geschrieben und liegen hier keine ungeflushten Inkremente, wird frisch geladen.
Im Gateway-Prozess selbst ist nach jedem Flush Datei == Speicher → der
mtime-Vergleich lädt dort nie unnötig nach."""
global _stats
with _lock:
if _stats is not None and not _dirty:
mtime = _stat_mtime()
if mtime is not None and mtime != _disk_mtime:
_stats = _load_from_disk()
return json.loads(json.dumps(_ensure_loaded()))
+15
View File
@@ -57,6 +57,9 @@ systemctl --user disable --now hermes-webui 2>/dev/null || true
rm -f "$HOME/.config/systemd/user/hermes-terminal.service" "$HOME/.config/systemd/user/hermes-webui.service"
# Box-Konsole (ttyd -> Login-Shell auf :7682), in MC2 als Konsole-Seite eingebettet.
cp "$SRC/deploy/box-console.service" "$HOME/.config/systemd/user/box-console.service"
# MC2-Gateway (UMBAU v3 P1): der /v1-Datenpfad als eigener Prozess (Loopback :9010,
# Restart=always) — Lucys Hirn + Nacht-Autonomie überleben damit jeden MC2-Neustart.
cp "$SRC/deploy/mc2-gateway.service" "$HOME/.config/systemd/user/mc2-gateway.service"
# Mem0-Sidecar-Unit (nur wenn das venv existiert).
[ -x "$HOME/.mem0/venv/bin/python" ] && cp "$SRC/deploy/mem0-service.service" "$HOME/.config/systemd/user/mem0-service.service"
# Voice-Sidecar-Unit (nur wenn das venv existiert).
@@ -171,6 +174,7 @@ if [ -d "$_HUI_WEB" ] && [ -x "$HOME/.hermes/node/bin/npx" ]; then
fi
systemctl --user daemon-reload
systemctl --user enable mission-control-2 >/dev/null 2>&1 || true
systemctl --user enable mc2-gateway >/dev/null 2>&1 || true
systemctl --user enable box-console >/dev/null 2>&1 || true
systemctl --user enable mem0-service >/dev/null 2>&1 || true
systemctl --user enable voice-service >/dev/null 2>&1 || true
@@ -191,11 +195,22 @@ loginctl enable-linger "$USER" >/dev/null 2>&1 || true
# reset-failed, damit ein zuvor am Start-Limit gestorbener Dienst wieder anläuft.
systemctl --user reset-failed hermes-builtin-ui 2>/dev/null || true
systemctl --user restart hermes-builtin-ui 2>/dev/null || true
# Gateway VOR dem Steuerpult (re)starten — MC2s /v1-Weiterleiter braucht ihn sofort.
# KEIN `|| true`: ohne Gateway ist der LLM-Datenpfad tot, das MUSS den Deploy stoppen.
systemctl --user restart mc2-gateway
systemctl --user restart mission-control-2
command -v ttyd >/dev/null 2>&1 && systemctl --user restart box-console 2>/dev/null || true
sleep 2
echo "--- Health ---"
# Gateway-Kaltstart-Fenster: bis ~12 s tolerieren, DANN hart scheitern (ohne Gateway
# ist der LLM-Datenpfad tot — das muss den Karten-Runner rot machen).
for _i in $(seq 1 10); do
curl -sf -m 3 http://127.0.0.1:9010/gw/health >/dev/null 2>&1 && break
[ "$_i" -eq 10 ] && { echo "FEHLER: mc2-gateway (:9010) antwortet nicht"; exit 1; }
sleep 1
done
curl -sf http://127.0.0.1:9010/gw/health && echo
curl -sf http://127.0.0.1:9001/api/health && echo
# Warm-Set (Hirn + Augen/vision + Gedächtnis/embed) nach dem Deploy nachladen — ein Deploy bzw.
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Gateway-Cutover (UMBAU v3 P1, Stufe 2 = Unabhängigkeit):
# Hermes' LLM-Endpunkte vom Steuerpult-Umweg (127.0.0.1:9001/v1) auf den
# eigenständigen Gateway (127.0.0.1:9010/v1) umstellen — Top-Level-config.yaml
# UND alle Profil-Configs (werkstatt/betrieb). Danach überleben Lucys Hirn und
# die komplette Nacht-Autonomie jeden MC2-Neustart.
#
# BEWUSST NICHT in deploy.sh: ~/.hermes/config.yaml ist Zwei-Schreiber-sensibel
# (Hermes schreibt sie selbst). Einmalig von Hand bzw. per angenommener Karte:
# bash ~/mission-control-v2/deploy/gateway-cutover.sh # umstellen
# bash ~/mission-control-v2/deploy/gateway-cutover.sh --revert # zurück
# Je geänderter Datei entsteht ein Backup *.bak-gwcut-<Zeitstempel>.
# Danach: hermes-gateway-Neustart + kurzer Funktionsnachweis.
set -euo pipefail
if [ "${1:-}" = "--revert" ]; then
ALT="127.0.0.1:9010/v1"; NEU="127.0.0.1:9001/v1"
PROBE="http://127.0.0.1:9001/api/health" # Rückweg: MC2 muss /v1 wieder selbst bedienen
else
ALT="127.0.0.1:9001/v1"; NEU="127.0.0.1:9010/v1"
PROBE="http://127.0.0.1:9010/gw/health" # Hinweg: Gateway-Prozess muss leben
fi
# Sicherung: NIE auf ein totes Ziel umstellen — das würde Lucy das Hirn abschneiden.
if ! curl -sf -m 5 "$PROBE" >/dev/null 2>&1; then
echo "ABBRUCH: Ziel $NEU antwortet nicht ($PROBE). Nichts geändert."
exit 1
fi
STAMP="$(date +%Y%m%d-%H%M%S)"
ALT_RE="${ALT//./\\.}" # Punkte für sed escapen
geaendert=0
for f in "$HOME/.hermes/config.yaml" "$HOME"/.hermes/profiles/*/config.yaml; do
[ -f "$f" ] || continue
grep -q "$ALT" "$f" || continue
cp "$f" "$f.bak-gwcut-$STAMP"
sed -i "s|$ALT_RE|$NEU|g" "$f"
echo "umgestellt: $f (Backup: $f.bak-gwcut-$STAMP)"
geaendert=1
done
if [ "$geaendert" -eq 0 ]; then
echo "Nichts zu tun — '$ALT' kommt in keiner Hermes-Config vor."
exit 0
fi
echo "hermes-gateway neu starten …"
systemctl --user restart hermes-gateway
sleep 3
if systemctl --user is-active --quiet hermes-gateway; then
echo "hermes-gateway läuft."
else
echo "WARN: hermes-gateway ist nach dem Neustart NICHT aktiv!"
echo " journalctl --user -u hermes-gateway -n 30 und ggf.: $0 --revert"
exit 1
fi
echo "Empfohlener Nachweis: cd ~/.hermes/hermes-agent && venv/bin/hermes doctor"
echo "FERTIG. Rückweg jederzeit: $0 --revert"
+26
View File
@@ -0,0 +1,26 @@
# systemd-USER-Unit für den MC2-Gateway — der /v1-Datenpfad als eigener Prozess (UMBAU v3 P1).
# Durch diesen Prozess läuft JEDER LLM-Aufruf der Box (Lucys Hirn, Nacht-Crons, Worker-
# Delegation, Vision) — er ist bewusst winzig, wird praktisch nie angefasst und startet
# sich immer selbst neu (Restart=always: die Wirbelsäule steht, bevor es jemand merkt).
# Loopback-only (:9010): alle kritischen Verbraucher sind box-lokal; LAN-Clients
# (IDE-Lane) kommen weiter über MC2 :9001/v1, das roh hierher durchreicht.
# Sudo-frei wie alle MC2-Units: ~/.config/systemd/user/ + systemctl --user.
[Unit]
Description=MC2 Gateway (/v1-Datenpfad: model:auto-Routing + Bild-Weiche)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=%h/mission-control-v2/backend
ExecStart=%h/mission-control-v2/backend/.venv/bin/python -m uvicorn gateway_app:app --host 127.0.0.1 --port 9010
Environment=PYTHONPATH=%h/mission-control-v2
Environment=MC_LLAMA_SWAP_URL=http://127.0.0.1:8080
Environment=MC_CONFIG_PATH=/etc/llama-swap/config.yaml
Environment=MC_MODELS_DIR=/srv/models
Restart=always
RestartSec=2
[Install]
WantedBy=default.target
+4
View File
@@ -21,6 +21,10 @@ Environment=MC_CONFIG_PATH=/etc/llama-swap/config.yaml
Environment=MC_MODELS_DIR=/srv/models
# Geteiltes Gedächtnis = die bestehende v1-DB (Kontinuität bis/über Cutover).
Environment=MC_MEMORY_DB=/srv/models/mission-control-memory.db
# Gateway-Auszug (UMBAU v3 P1): /v1 roh an den eigenständigen mc2-gateway-Prozess
# durchreichen. Diese Zeile entfernen (+ daemon-reload + restart) = Rollback, MC2
# bedient /v1 wieder selbst.
Environment=MC_V1_UPSTREAM=http://127.0.0.1:9010
# KEIN MC_ENGINE_UPDATE_CMD-Override mehr: Das alte /usr/local/bin/update-llamacpp zog den
# ROCm-Build nach /opt/llamacpp (totes Rollback-Dir) statt des aktiven Vulkan-Builds → Updates
# liefen ins Leere ("DONE", aber nichts passierte). Ohne Override nutzt das Backend den Default
+38
View File
@@ -17,6 +17,7 @@ Recherche). DIE Arbeitsliste für die Zeit nach Claude. Leser: der User, die Box
- Warm-Set schlank: Hirn 17,5 GB + embed 2 GB + reranker 2,2 GB ≈ 22 GB.
- Repo: lokal = Gitea = Box @ main. Live-Checkout wieder sauber auf `main`.
- **Autonomie-Beweis der Nacht 14.07.:** Idle-Radar erhob Journal-Befund (stop-sigterm-Timeout) → Decomposer zerlegte → werkstatt baute Restart-Skript+systemd-Override → betrieb testete live (~6 s Recovery) → done. Ohne Menschen, ohne Claude.
- **★ Nachmittags-Befund (Greenfield-Review + Live-Inspektion 15.07.):** JEDER LLM-Aufruf der Box läuft durch den EINEN MC2-Prozess auf :9001 — Lucys Haupt-Hirn (`model.base_url`, config.yaml ~Z. 430), alle auxiliary-Rollen (vision/curator/kanban_decomposer/triage_specifier), die komplette `delegation` (jeder Worker-Auftrag) und 3 MCP-Server (MC_URL). Der am häufigsten neu gestartete Dienst ist zugleich die Wirbelsäule des gesamten Stacks; das `Restart=always`-Override ist rückblickend ein Symptom dieser Kopplung. Konsequenz: **Abschnitt 3b · UMBAU v3.**
## 1 · Heute im Review erledigt (15.07.)
@@ -49,6 +50,43 @@ c) **sudo-Runde (einmal Passwort):**
2. Root-Warmup-Kopie aktualisieren: `sudo install -m 0755 ~/mission-control-v2/deploy/warmup.sh /usr/local/bin/llama-swap-warmup.sh` (stale seit 07.07. — sonst wärmt ein llama-swap-Restart falsch)
d) **Daily-Briefing-Cron** (dein eigener, 08:00): steht auf `Repeat: 9/41`**läuft nach 41 Läufen aus**. Wenn dauerhaft gewollt: auf ∞ stellen (`hermes cron edit 095d53c1e99e`).
## 3b · UMBAU v3 — Architektur-Feinschliff (NEU 15.07. nachmittags)
**Woher:** Greenfield-Gedankenexperiment („wie sähe ein Komplett-Rework aus?") + Live-Inspektion
der Box. Ergebnis: KEIN Rewrite (die Box wartet sich nachweislich selbst — während der Inspektion
lief Karte `t_ab6754f1` und lud das Gemma-4-Bench-Modell), sondern vier Inkremente, die je für
sich Wert haben. Reihenfolge ist von der Topologie diktiert, nicht verhandelbar: P1 ent-riskiert
alles Weitere. Recherche-Verdikte dazu (15.07.): FastAPI bleibt (Litestar erwogen — Ökosystem/
Coder-Modell-Wissen gewinnen), React bleibt (Svelte/Solid schneller, aber irrelevant für
1-Nutzer-LAN), SSE ist 2026 der klare Dashboard-Standard (einweg, Auto-Reconnect, ~95 % der Fälle).
- **P1 · Gateway-Auszug** ✅ GEBAUT (Branch `umbau/p1-gateway-auszug`, wartet auf Klick):
Der /v1-Datenpfad wird eigener Prozess `mc2-gateway.service` (Loopback **:9010**,
`Restart=always`, gleicher Router-Code `routers/gateway_proxy.py`, neuer Einstieg
`backend/gateway_app.py`). MC2 :9001/v1 wird dünner Roh-Weiterleiter (`MC_V1_UPSTREAM` in der
Unit; Zeile entfernen = Rollback), damit LAN-Clients (IDE-Lane) nichts merken. Token-Zählung
passiert genau EINMAL (im Gateway); `token_stats.get_stats()` lädt bei Fremd-Änderung per
mtime nach (Steuerpult zeigt sonst eingefrorene Zahlen). **Stufe 2 = Unabhängigkeit:**
`deploy/gateway-cutover.sh` stellt ~/.hermes-Configs (Top-Level + beide Profile) von
`127.0.0.1:9001/v1` auf `127.0.0.1:9010/v1` um (Backups, `--revert`, bewusst NICHT im
deploy.sh — config.yaml ist Zwei-Schreiber-sensibel). Danach überlebt Lucys Hirn + die ganze
Nacht-Autonomie jeden MC2-Neustart. Postcheck prüft den Gateway mit (nur wenn Unit enabled).
- **P2 · Steward** (Karte, nach P1): die 4 Endlos-Loops (warmer, sentry, reminders, Mem0-dedupe)
aus dem Webserver-Lifespan in einen eigenen Mini-Dienst — ein Steuerpult-Neustart darf keinem
Wächter Timing/Gedächtnis nehmen. Danach gibt es genau ZWEI Zeitplan-Systeme mit klaren Rollen:
systemd-Timer (Box-Pflege) + Hermes-Crons (Agenten-Arbeit).
- **P3 · Steuerpult-Modernisierung** (Kartenserie, reine UI/API-Arbeit, berührt nach P1 nie mehr
den Datenpfad): (a) EIN SSE-Eventstrom `/api/events` ersetzt die 53 `refetchInterval`-Poller in
`queries.ts`; (b) TanStack Router → echte URLs (`/modelle/coder`, `/auftrag/t_…`), Deep-Links,
Browser-Zurück; (c) TypeScript-Typen aus OpenAPI GENERIEREN (openapi-typescript) statt
handgepflegtem `api.ts` (stiller Drift-Fehlerherd).
- **P4 · State-Konsolidierung** (optional, NUR bei Schmerz): die 10 `mc2-*.json/jsonl` in
/srv/models → eine SQLite (WAL) mit Event-Tabelle (Chronik/Zeitmaschine = Abfragen statt
Eigenbau; Backup = eine Datei). Nutzen real, aber Risiko/Aufwand hoch — bewusst hinten.
**Bewusst NICHT geändert:** FastAPI, React/Vite/Tailwind, kein Electron, kein Chat in MC2,
LAN-Trust ohne Auth, Appliance-Philosophie (user-services, deploy.sh, Postcheck, Rollback).
## 4 · ROBUSTHEIT (Priorität 1 — Karten für die Queue)
- **R1 · Worker-Protokoll-Erinnerung** (klein): `box-steckbrief-inject.sh` um einen Satz ergänzen: „Am Ende IMMER kanban_complete oder kanban_block aufrufen — sauberes Text-Ende zählt nicht." Genau daran blockierte die Nacht-Karte.
@@ -1,4 +1,4 @@
import{c as k,R as _,m as S,b as C,u as H,r as E,j as e,U as A,e as a,V as u,W as p,Y as B,s as h,Z as D,Q as P,X as M,C as g,g as b,q as i}from"./index-CgQUayy8.js";/**
import{c as k,R as _,m as S,b as C,u as H,r as E,j as e,U as A,e as a,V as u,W as p,Y as B,s as h,Z as D,Q as P,X as M,C as g,g as b,q as i}from"./index-CDslgvwS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as O,a4 as X,u as _,b as Q,r as h,j as e,a5 as Y,e as S,L as g,C as q,N as U,a6 as ee,a7 as te,O as V,X as I,w as R,F,a8 as re,T as z,g as B,a9 as se,q as J}from"./index-CgQUayy8.js";import{L as ae}from"./lightbulb-Dj1hbza2.js";import{S as G}from"./send-BjlsoGEB.js";/**
import{c as O,a4 as X,u as _,b as Q,r as h,j as e,a5 as Y,e as S,L as g,C as q,N as U,a6 as ee,a7 as te,O as V,X as I,w as R,F,a8 as re,T as z,g as B,a9 as se,q as J}from"./index-CDslgvwS.js";import{L as ae}from"./lightbulb-Cfb98djJ.js";import{S as G}from"./send-BuNKVXpO.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as h,aa as S,r as c,j as e,ab as z,L as g,N as M,ac as C,a9 as D,ad as A,ae as B,af as Z,e as E,ag as L,u as q,b as R,g as k,q as T}from"./index-CgQUayy8.js";/**
import{c as h,aa as S,r as c,j as e,ab as z,L as g,N as M,ac as C,a9 as D,ad as A,ae as B,af as Z,e as E,ag as L,u as q,b as R,g as k,q as T}from"./index-CDslgvwS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{c as p,j as e,r as M,s as C,a0 as z,a1 as k,a2 as y,W as v,Q as L,a3 as G,Z as T,e as f,N as D}from"./index-CgQUayy8.js";import{B as w}from"./book-open-ClD73ijg.js";import{a as N,b as B,L as P,C as E,Z as R}from"./zap-B7eYUqmc.js";import{L as I}from"./lightbulb-Dj1hbza2.js";/**
import{c as p,j as e,r as M,s as C,a0 as z,a1 as k,a2 as y,W as v,Q as L,a3 as G,Z as T,e as f,N as D}from"./index-CDslgvwS.js";import{B as w}from"./book-open-kUtGvGMW.js";import{a as N,b as B,L as P,C as E,Z as R}from"./zap-BFGxSGSP.js";import{L as I}from"./lightbulb-Cfb98djJ.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1 +1 @@
import{R as l,V as o,j as e,e as a,U as n,T as d,$ as i}from"./index-CgQUayy8.js";function c(){const{data:s}=l(),r=s!=null&&s.box_console_url?o(s.box_console_url):void 0,t=s==null?void 0:s.box_console_reachable;return e.jsxs("div",{className:"flex h-full flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Konsole"}),e.jsx("p",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:"Direkte Shell auf der Box — wie ein SSH-Fenster, mitten im Browser."})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground",children:[e.jsx("span",{className:a("h-2 w-2 rounded-full",t?"bg-emerald-500 animate-pulse":"bg-amber-500")}),t?"online":"offline"]}),r&&e.jsxs("a",{href:r,target:"_blank",rel:"noopener",className:"flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground",children:[e.jsx(n,{className:"h-3.5 w-3.5"})," In neuem Tab"]})]})]}),r?e.jsxs("div",{className:"relative min-h-[58vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25",children:[t===!1&&e.jsxs("div",{className:"absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center",children:[e.jsx(d,{className:"h-8 w-8 text-amber-400"}),e.jsx("div",{className:"text-sm font-semibold text-amber-300",children:"Konsole nicht erreichbar"}),e.jsxs("p",{className:"max-w-sm text-[11px] leading-normal text-muted-foreground",children:["Der ",e.jsx("code",{className:"font-mono text-primary",children:"box-console"}),"-Dienst (ttyd) läuft nicht. Auf der Box: ",e.jsx("code",{className:"font-mono",children:"systemctl --user restart box-console"}),"."]})]}),e.jsx("iframe",{src:r,title:"Box-Konsole",className:"h-full w-full border-0",style:{minHeight:"58vh"}})]}):e.jsxs("div",{className:"flex min-h-[58vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground",children:[e.jsx(i,{className:"mr-2 h-4 w-4"})," Lade Konsole…"]})]})}export{c as KonsoleView};
import{R as l,V as o,j as e,e as a,U as n,T as d,$ as i}from"./index-CDslgvwS.js";function c(){const{data:s}=l(),r=s!=null&&s.box_console_url?o(s.box_console_url):void 0,t=s==null?void 0:s.box_console_reachable;return e.jsxs("div",{className:"flex h-full flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Konsole"}),e.jsx("p",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:"Direkte Shell auf der Box — wie ein SSH-Fenster, mitten im Browser."})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground",children:[e.jsx("span",{className:a("h-2 w-2 rounded-full",t?"bg-emerald-500 animate-pulse":"bg-amber-500")}),t?"online":"offline"]}),r&&e.jsxs("a",{href:r,target:"_blank",rel:"noopener",className:"flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground",children:[e.jsx(n,{className:"h-3.5 w-3.5"})," In neuem Tab"]})]})]}),r?e.jsxs("div",{className:"relative min-h-[58vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25",children:[t===!1&&e.jsxs("div",{className:"absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center",children:[e.jsx(d,{className:"h-8 w-8 text-amber-400"}),e.jsx("div",{className:"text-sm font-semibold text-amber-300",children:"Konsole nicht erreichbar"}),e.jsxs("p",{className:"max-w-sm text-[11px] leading-normal text-muted-foreground",children:["Der ",e.jsx("code",{className:"font-mono text-primary",children:"box-console"}),"-Dienst (ttyd) läuft nicht. Auf der Box: ",e.jsx("code",{className:"font-mono",children:"systemctl --user restart box-console"}),"."]})]}),e.jsx("iframe",{src:r,title:"Box-Konsole",className:"h-full w-full border-0",style:{minHeight:"58vh"}})]}):e.jsxs("div",{className:"flex min-h-[58vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground",children:[e.jsx(i,{className:"mr-2 h-4 w-4"})," Lade Konsole…"]})]})}export{c as KonsoleView};
@@ -1,5 +1,5 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/GraphView-BrCeW1FA.js","assets/index-CgQUayy8.js","assets/index-Bpukq9Y7.css"])))=>i.map(i=>d[i]);
var ke=Object.defineProperty;var ve=(d,i,c)=>i in d?ke(d,i,{enumerable:!0,configurable:!0,writable:!0,value:c}):d[i]=c;var U=(d,i,c)=>ve(d,typeof i!="symbol"?i+"":i,c);import{c as h,r as s,j as e,u as Ne,b as Se,I as J,J as Ce,C as W,K as Ee,S as Me,M as ze,N as _,e as x,O as _e,Q as Oe,X,v as Z,_ as Te,g as j}from"./index-CgQUayy8.js";import{C as Y}from"./copy-B1SWMsx4.js";import{S as Ae}from"./send-BjlsoGEB.js";import{B as De}from"./book-open-ClD73ijg.js";/**
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/GraphView-BPfxMmZZ.js","assets/index-CDslgvwS.js","assets/index-Bhpy_OBa.css"])))=>i.map(i=>d[i]);
var ke=Object.defineProperty;var ve=(d,i,c)=>i in d?ke(d,i,{enumerable:!0,configurable:!0,writable:!0,value:c}):d[i]=c;var U=(d,i,c)=>ve(d,typeof i!="symbol"?i+"":i,c);import{c as h,r as s,j as e,u as Ne,b as Se,I as J,J as Ce,C as W,K as Ee,S as Me,M as ze,N as _,e as x,O as _e,Q as Oe,X,v as Z,_ as Te,g as j}from"./index-CDslgvwS.js";import{C as Y}from"./copy-B32DycGg.js";import{S as Ae}from"./send-BuNKVXpO.js";import{B as De}from"./book-open-kUtGvGMW.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -34,7 +34,7 @@ var ke=Object.defineProperty;var ve=(d,i,c)=>i in d?ke(d,i,{enumerable:!0,config
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const Re=h("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);class re extends s.Component{constructor(){super(...arguments);U(this,"state",{error:null})}static getDerivedStateFromError(c){return{error:c}}render(){return this.state.error?e.jsxs("div",{className:"h-[480px] rounded-2xl border border-red-500/20 bg-red-500/5 p-6 text-xs text-red-400 overflow-auto",children:[e.jsx("div",{className:"font-semibold mb-2",children:"Graph konnte nicht gerendert werden"}),e.jsx("pre",{className:"whitespace-pre-wrap break-words text-[11px] leading-relaxed",children:this.state.error.message})]}):this.props.children}}const te=s.lazy(()=>Te(()=>import("./GraphView-BrCeW1FA.js"),__vite__mapDeps([0,1,2])).then(d=>({default:d.GraphView}))),k=["identity","knowledge","rules","events"],se=new Set(["auto","agent","hermes"]),O={identity:{label:"Identität",icon:Re,bg:"bg-cyan-500/10",text:"text-cyan-400"},knowledge:{label:"Wissen",icon:Oe,bg:"bg-indigo-500/10",text:"text-indigo-400"},rules:{label:"Regeln",icon:Pe,bg:"bg-violet-500/10",text:"text-violet-400"},events:{label:"Ereignisse",icon:_e,bg:"bg-amber-500/10",text:"text-amber-400"}},ne={label:"Gedächtnis",icon:De,text:"text-muted-foreground"},Ve={identity:"border-l-cyan-500/80",knowledge:"border-l-indigo-500/80",rules:"border-l-violet-500/80",events:"border-l-amber-500/80"},ae=`Hi Hermes! Lass uns ein kurzes Onboarding machen, damit du dich künftig an mich erinnerst. Stell mir nacheinander ein paar kurze Fragen zu:
*/const Re=h("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);class re extends s.Component{constructor(){super(...arguments);U(this,"state",{error:null})}static getDerivedStateFromError(c){return{error:c}}render(){return this.state.error?e.jsxs("div",{className:"h-[480px] rounded-2xl border border-red-500/20 bg-red-500/5 p-6 text-xs text-red-400 overflow-auto",children:[e.jsx("div",{className:"font-semibold mb-2",children:"Graph konnte nicht gerendert werden"}),e.jsx("pre",{className:"whitespace-pre-wrap break-words text-[11px] leading-relaxed",children:this.state.error.message})]}):this.props.children}}const te=s.lazy(()=>Te(()=>import("./GraphView-BPfxMmZZ.js"),__vite__mapDeps([0,1,2])).then(d=>({default:d.GraphView}))),k=["identity","knowledge","rules","events"],se=new Set(["auto","agent","hermes"]),O={identity:{label:"Identität",icon:Re,bg:"bg-cyan-500/10",text:"text-cyan-400"},knowledge:{label:"Wissen",icon:Oe,bg:"bg-indigo-500/10",text:"text-indigo-400"},rules:{label:"Regeln",icon:Pe,bg:"bg-violet-500/10",text:"text-violet-400"},events:{label:"Ereignisse",icon:_e,bg:"bg-amber-500/10",text:"text-amber-400"}},ne={label:"Gedächtnis",icon:De,text:"text-muted-foreground"},Ve={identity:"border-l-cyan-500/80",knowledge:"border-l-indigo-500/80",rules:"border-l-violet-500/80",events:"border-l-amber-500/80"},ae=`Hi Hermes! Lass uns ein kurzes Onboarding machen, damit du dich künftig an mich erinnerst. Stell mir nacheinander ein paar kurze Fragen zu:
1) wer ich bin und woran ich gerade arbeite,
2) wie ich angesprochen werden möchte,
3) meine bevorzugten Tools, Sprachen und Arbeitsweise,
@@ -1,4 +1,4 @@
import{c as D,u as Ce,a as Ee,b as Se,j as e,f as ce,d as De,e as g,g as B,q as te,B as ae,h as qe,E as _e,i as Ge,r as y,X as Re,C as J,T as re,k as Z,H as Ae,l as P,m as me,n as Fe,o as Ke,p as Oe,S as pe,s as Ie,P as Te,t as Ue,v as He,w as Le,x as Pe,y as Qe}from"./index-CgQUayy8.js";import{C as xe,Z as ue,L as Ve,a as We,b as Je}from"./zap-B7eYUqmc.js";/**
import{c as D,u as Ce,a as Ee,b as Se,j as e,f as ce,d as De,e as g,g as B,q as te,B as ae,h as qe,E as _e,i as Ge,r as y,X as Re,C as J,T as re,k as Z,H as Ae,l as P,m as me,n as Fe,o as Ke,p as Oe,S as pe,s as Ie,P as Te,t as Ue,v as He,w as Le,x as Pe,y as Qe}from"./index-CDslgvwS.js";import{C as xe,Z as ue,L as Ve,a as We,b as Je}from"./zap-BFGxSGSP.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{ah as y,r as x,g as L,j as e,L as v,S,ai as W,N as C,e as w,aj as E}from"./index-CgQUayy8.js";import{F as M}from"./folder-open-tYLNcEQ3.js";const D={"":"Allgemein",traeume:"Träume",muster:"Muster","skill-kandidaten":"Skill-Kandidaten","skill-kandidaten/beauftragt":"Skill-Kandidaten · beauftragt","skill-kandidaten/verworfen":"Skill-Kandidaten · verworfen"};function T(){const{data:o,isLoading:h}=y(),[l,f]=x.useState(null),[d,r]=x.useState(null),[u,i]=x.useState(!1),[a,p]=x.useState(""),t=x.useMemo(()=>(o==null?void 0:o.files)??[],[o]),m=x.useMemo(()=>{const s=new Map;for(const n of t)s.set(n.name.toLowerCase(),n.path);return s},[t]),g=x.useMemo(()=>{if(!a.trim())return t;const s=a.toLowerCase();return t.filter(n=>n.title.toLowerCase().includes(s)||n.path.toLowerCase().includes(s))},[t,a]),b=x.useMemo(()=>{const s=[];for(const n of g){const c=s.find(k=>k.dir===n.dir);c?c.files.push(n):s.push({dir:n.dir,files:[n]})}return s.sort((n,c)=>n.dir===""?-1:c.dir===""?1:n.dir.localeCompare(c.dir))},[g]);x.useEffect(()=>{if(!l&&t.length){const s=t.find(n=>n.path.toLowerCase()==="index.md");f((s==null?void 0:s.path)??t[0].path)}},[t,l]),x.useEffect(()=>{l&&(i(!0),L(`/api/wissen/datei?pfad=${encodeURIComponent(l)}`).then(r).catch(()=>r({path:l,content:"*(Notiz nicht ladbar)*",mtime:0})).finally(()=>i(!1)))},[l]);const j=s=>{const n=m.get(s.toLowerCase());n&&f(n)};return e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Wissen"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:"Was sich die Box nachts erschließt — Traum-Notizen, Muster und Skill-Ideen aus dem Wissens-Vault, verlinkt wie ein Wiki."})]}),o&&!o.available&&e.jsx("div",{className:"rounded-2xl border border-amber-500/30 bg-amber-500/5 p-4 text-xs text-amber-300",children:"Der Wissens-Vault liegt auf der Box (~/wissens-vault) — im lokalen Dev-Modus nicht verfügbar."}),h&&e.jsxs("div",{className:"flex items-center gap-2 rounded-2xl border border-border/60 bg-card/30 p-6 text-xs text-muted-foreground",children:[e.jsx(v,{className:"h-4 w-4 animate-spin"})," Vault wird geladen …"]}),(o==null?void 0:o.available)&&t.length===0&&e.jsx("div",{className:"rounded-2xl border border-dashed border-border/60 bg-card/20 p-8 text-center text-xs text-muted-foreground",children:"Noch keine Notizen. Der nächtliche Traum (03:15) legt sie an — morgen früh steht hier die erste."}),t.length>0&&e.jsxs("div",{className:"flex flex-col gap-4 lg:flex-row",children:[e.jsxs("aside",{className:"w-full shrink-0 space-y-3 lg:w-72",children:[e.jsxs("div",{className:"relative",children:[e.jsx("input",{value:a,onChange:s=>p(s.target.value),type:"search",placeholder:"Notizen durchsuchen…","aria-label":"Notizen durchsuchen",className:"h-9 w-full rounded-lg border border-border/60 bg-card/45 pl-8 pr-3 text-xs text-foreground outline-none focus:ring-1 focus:ring-primary/50"}),e.jsx(S,{className:"absolute left-2.5 top-2.5 h-3.5 w-3.5 text-muted-foreground"})]}),e.jsx("div",{className:"max-h-[70vh] space-y-3 overflow-y-auto pr-1 scrollbar-thin",children:b.map(({dir:s,files:n})=>e.jsxs("div",{children:[e.jsxs("p",{className:"mb-1 flex items-center gap-1 text-[10px] font-bold uppercase tracking-wider text-muted-foreground/60",children:[e.jsx(M,{className:"h-3 w-3"})," ",D[s]??s]}),e.jsx("div",{className:"space-y-0.5",children:n.map(c=>e.jsxs("button",{onClick:()=>f(c.path),className:w("flex w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left text-xs transition-all cursor-pointer",l===c.path?"bg-primary/15 text-primary":"text-muted-foreground hover:bg-accent hover:text-foreground"),children:[e.jsx(W,{className:"h-3.5 w-3.5 shrink-0"}),e.jsx("span",{className:"truncate",title:c.title,children:c.name}),c.neu&&e.jsxs("span",{className:"ml-auto flex items-center gap-0.5 rounded bg-violet-500/15 px-1 py-0.5 text-[8px] font-bold uppercase text-violet-300",children:[e.jsx(C,{className:"h-2.5 w-2.5"}),"neu"]})]},c.path))})]},s||"__root"))})]}),e.jsx("div",{className:"min-w-0 flex-1 mc-card p-5",children:u?e.jsxs("div",{className:"flex items-center gap-2 text-xs text-muted-foreground",children:[e.jsx(v,{className:"h-4 w-4 animate-spin"})," Notiz wird geladen …"]}):d?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-3 flex items-center justify-between border-b border-border/40 pb-2",children:[e.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-[11px] text-muted-foreground",children:[e.jsx(E,{className:"h-3.5 w-3.5"})," ",d.path]}),d.mtime>0&&e.jsxs("span",{className:"text-[10px] text-muted-foreground/60",children:["Stand ",new Date(d.mtime*1e3).toLocaleString("de-DE",{day:"2-digit",month:"2-digit",hour:"2-digit",minute:"2-digit"})]})]}),e.jsx(z,{text:d.content,onWikiLink:j,known:m})]}):null})]})]})}function z({text:o,onWikiLink:h,known:l}){const f=o.split(`
import{aj as y,r as x,g as L,j as e,L as v,S,ak as W,N as C,e as w,al as E}from"./index-CDslgvwS.js";import{F as M}from"./folder-open-D9Tf3AHU.js";const D={"":"Allgemein",traeume:"Träume",muster:"Muster","skill-kandidaten":"Skill-Kandidaten","skill-kandidaten/beauftragt":"Skill-Kandidaten · beauftragt","skill-kandidaten/verworfen":"Skill-Kandidaten · verworfen"};function T(){const{data:o,isLoading:h}=y(),[l,f]=x.useState(null),[d,r]=x.useState(null),[u,i]=x.useState(!1),[a,p]=x.useState(""),t=x.useMemo(()=>(o==null?void 0:o.files)??[],[o]),m=x.useMemo(()=>{const s=new Map;for(const n of t)s.set(n.name.toLowerCase(),n.path);return s},[t]),g=x.useMemo(()=>{if(!a.trim())return t;const s=a.toLowerCase();return t.filter(n=>n.title.toLowerCase().includes(s)||n.path.toLowerCase().includes(s))},[t,a]),b=x.useMemo(()=>{const s=[];for(const n of g){const c=s.find(k=>k.dir===n.dir);c?c.files.push(n):s.push({dir:n.dir,files:[n]})}return s.sort((n,c)=>n.dir===""?-1:c.dir===""?1:n.dir.localeCompare(c.dir))},[g]);x.useEffect(()=>{if(!l&&t.length){const s=t.find(n=>n.path.toLowerCase()==="index.md");f((s==null?void 0:s.path)??t[0].path)}},[t,l]),x.useEffect(()=>{l&&(i(!0),L(`/api/wissen/datei?pfad=${encodeURIComponent(l)}`).then(r).catch(()=>r({path:l,content:"*(Notiz nicht ladbar)*",mtime:0})).finally(()=>i(!1)))},[l]);const j=s=>{const n=m.get(s.toLowerCase());n&&f(n)};return e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Wissen"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:"Was sich die Box nachts erschließt — Traum-Notizen, Muster und Skill-Ideen aus dem Wissens-Vault, verlinkt wie ein Wiki."})]}),o&&!o.available&&e.jsx("div",{className:"rounded-2xl border border-amber-500/30 bg-amber-500/5 p-4 text-xs text-amber-300",children:"Der Wissens-Vault liegt auf der Box (~/wissens-vault) — im lokalen Dev-Modus nicht verfügbar."}),h&&e.jsxs("div",{className:"flex items-center gap-2 rounded-2xl border border-border/60 bg-card/30 p-6 text-xs text-muted-foreground",children:[e.jsx(v,{className:"h-4 w-4 animate-spin"})," Vault wird geladen …"]}),(o==null?void 0:o.available)&&t.length===0&&e.jsx("div",{className:"rounded-2xl border border-dashed border-border/60 bg-card/20 p-8 text-center text-xs text-muted-foreground",children:"Noch keine Notizen. Der nächtliche Traum (03:15) legt sie an — morgen früh steht hier die erste."}),t.length>0&&e.jsxs("div",{className:"flex flex-col gap-4 lg:flex-row",children:[e.jsxs("aside",{className:"w-full shrink-0 space-y-3 lg:w-72",children:[e.jsxs("div",{className:"relative",children:[e.jsx("input",{value:a,onChange:s=>p(s.target.value),type:"search",placeholder:"Notizen durchsuchen…","aria-label":"Notizen durchsuchen",className:"h-9 w-full rounded-lg border border-border/60 bg-card/45 pl-8 pr-3 text-xs text-foreground outline-none focus:ring-1 focus:ring-primary/50"}),e.jsx(S,{className:"absolute left-2.5 top-2.5 h-3.5 w-3.5 text-muted-foreground"})]}),e.jsx("div",{className:"max-h-[70vh] space-y-3 overflow-y-auto pr-1 scrollbar-thin",children:b.map(({dir:s,files:n})=>e.jsxs("div",{children:[e.jsxs("p",{className:"mb-1 flex items-center gap-1 text-[10px] font-bold uppercase tracking-wider text-muted-foreground/60",children:[e.jsx(M,{className:"h-3 w-3"})," ",D[s]??s]}),e.jsx("div",{className:"space-y-0.5",children:n.map(c=>e.jsxs("button",{onClick:()=>f(c.path),className:w("flex w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left text-xs transition-all cursor-pointer",l===c.path?"bg-primary/15 text-primary":"text-muted-foreground hover:bg-accent hover:text-foreground"),children:[e.jsx(W,{className:"h-3.5 w-3.5 shrink-0"}),e.jsx("span",{className:"truncate",title:c.title,children:c.name}),c.neu&&e.jsxs("span",{className:"ml-auto flex items-center gap-0.5 rounded bg-violet-500/15 px-1 py-0.5 text-[8px] font-bold uppercase text-violet-300",children:[e.jsx(C,{className:"h-2.5 w-2.5"}),"neu"]})]},c.path))})]},s||"__root"))})]}),e.jsx("div",{className:"min-w-0 flex-1 mc-card p-5",children:u?e.jsxs("div",{className:"flex items-center gap-2 text-xs text-muted-foreground",children:[e.jsx(v,{className:"h-4 w-4 animate-spin"})," Notiz wird geladen …"]}):d?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-3 flex items-center justify-between border-b border-border/40 pb-2",children:[e.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-[11px] text-muted-foreground",children:[e.jsx(E,{className:"h-3.5 w-3.5"})," ",d.path]}),d.mtime>0&&e.jsxs("span",{className:"text-[10px] text-muted-foreground/60",children:["Stand ",new Date(d.mtime*1e3).toLocaleString("de-DE",{day:"2-digit",month:"2-digit",hour:"2-digit",minute:"2-digit"})]})]}),e.jsx(z,{text:d.content,onWikiLink:j,known:m})]}):null})]})]})}function z({text:o,onWikiLink:h,known:l}){const f=o.split(`
`),d=[];let r=[],u=null;const i=a=>{r.length&&(d.push(e.jsx("ul",{className:"mb-3 ml-4 list-disc space-y-1",children:r},a)),r=[])};return f.forEach((a,p)=>{const t=`l${p}`;if(u!==null){a.trimEnd()==="```"?(d.push(e.jsx("pre",{className:"mb-3 overflow-x-auto rounded-xl border border-border/50 bg-background/50 p-3 font-mono text-[11px] leading-relaxed text-foreground/85",children:u.join(`
`)},t)),u=null):u.push(a);return}if(a.trimStart().startsWith("```")){i(t),u=[];return}const m=a.trimEnd();if(!m.trim()){i(t);return}const g=m.match(/^(#{1,4})\s+(.*)$/);if(g){i(t);const j=g[1].length,s=j===1?"text-lg font-bold mt-1 mb-3":j===2?"text-base font-bold mt-4 mb-2":"text-sm font-bold mt-3 mb-1.5";d.push(e.jsx("p",{className:w(s,"font-space text-foreground"),children:N(g[2],h,l,t)},t));return}const b=m.match(/^\s*[-*•]\s+(.*)$/);if(b){r.push(e.jsx("li",{className:"text-xs leading-relaxed text-foreground/85",children:N(b[1],h,l,t)},t));return}i(t),d.push(e.jsx("p",{className:"mb-2 text-xs leading-relaxed text-foreground/85",children:N(m,h,l,t)},t))}),i("end"),u!==null&&d.push(e.jsx("pre",{className:"mb-3 overflow-x-auto rounded-xl border border-border/50 bg-background/50 p-3 font-mono text-[11px]",children:u.join(`
`)},"code-end")),e.jsx("div",{className:"max-w-3xl",children:d})}function N(o,h,l,f){return o.split(/(\[\[[^\]]+\]\]|\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g).map((r,u)=>{const i=`${f}-${u}`,a=r.match(/^\[\[([^\]|]+)(?:\|([^\]]+))?\]\]$/);if(a){const p=a[1].trim(),t=(a[2]??a[1]).trim();return l.has(p.toLowerCase())?e.jsx("button",{onClick:()=>h(p),className:"rounded bg-primary/10 px-1 text-primary underline decoration-primary/40 underline-offset-2 hover:bg-primary/20 cursor-pointer",children:t},i):e.jsx("span",{className:"rounded bg-background/40 px-1 text-muted-foreground",title:"Notiz existiert (noch) nicht",children:t},i)}return r.startsWith("**")&&r.endsWith("**")?e.jsx("b",{className:"font-semibold text-foreground",children:r.slice(2,-2)},i):r.startsWith("*")&&r.endsWith("*")&&r.length>2?e.jsx("i",{children:r.slice(1,-1)},i):r.startsWith("`")&&r.endsWith("`")?e.jsx("code",{className:"rounded bg-background/50 px-1 font-mono text-[11px] text-teal-300",children:r.slice(1,-1)},i):e.jsx("span",{children:r},i)})}export{T as WissenView};
@@ -1,4 +1,4 @@
import{c as a}from"./index-CgQUayy8.js";/**
import{c as a}from"./index-CDslgvwS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
+6
View File
@@ -0,0 +1,6 @@
import{c}from"./index-CDslgvwS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const r=c("CircleX",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]]);export{r as C};
@@ -1,4 +1,4 @@
import{c}from"./index-CgQUayy8.js";/**
import{c}from"./index-CDslgvwS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as a}from"./index-CgQUayy8.js";/**
import{c as a}from"./index-CDslgvwS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More