feat(v9): Phase 8 — Cockpit zeigt Lernen & Aktivität
- hermes_control.py: learned_profile() liest USER.md (Curator-Nutzerprofil,
§ -getrennt) + MEMORY.md; insights() zieht Sessions/Tokens/Tool-Calls +
Top-Tools aus `hermes insights` (Regex, best effort).
- routers/hermes.py: GET /api/hermes/{learned,insights}.
- HermesPanel-Cockpit: Kachel "Aktivität" (Kennzahlen + Top-Tool-Balken) und
"Was Hermes über dich gelernt hat" (USER.md-Fakten) — beantwortet sichtbar,
dass der Agent automatisch mitlernt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -149,3 +149,58 @@ def skills() -> list[dict]:
|
||||
return rows
|
||||
|
||||
return _cached("skills", _load) # type: ignore[return-value]
|
||||
|
||||
|
||||
def learned_profile() -> dict:
|
||||
"""Was Hermes ueber den Nutzer gelernt hat (Phase 8).
|
||||
|
||||
`~/.hermes/memories/USER.md` ist das vom Curator destillierte Nutzerprofil
|
||||
(Eintraege mit `§` getrennt), `MEMORY.md` allgemeine gelernte Fakten. Direkter
|
||||
Beleg, dass der Agent automatisch mitlernt.
|
||||
"""
|
||||
def _read(name: str) -> str:
|
||||
try:
|
||||
return (HERMES_HOME / "memories" / name).read_text(errors="replace").strip()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _load() -> dict:
|
||||
user_raw = _read("USER.md")
|
||||
facts = [s.strip() for s in user_raw.split("§") if s.strip()]
|
||||
return {"user_facts": facts, "memory": _read("MEMORY.md")}
|
||||
|
||||
return _cached("learned", _load) # type: ignore[return-value]
|
||||
|
||||
|
||||
def insights() -> dict:
|
||||
"""Aktivitaets-Kennzahlen via `hermes insights` (Phase 8).
|
||||
|
||||
Die CLI rendert ein Box-Art-Layout (kein --json) -> Headline-Zahlen + Top-Tools
|
||||
per Regex herausziehen (best effort; faellt leer aus, wenn sich das Format aendert).
|
||||
"""
|
||||
def _num(pat: str, text: str):
|
||||
m = re.search(pat, text)
|
||||
return m.group(1).strip() if m else None
|
||||
|
||||
def _load() -> dict:
|
||||
txt = _run_cli(["insights", "--days", "30"])
|
||||
out: dict = {
|
||||
"sessions": _num(r"Sessions:\s+([\d,]+)", txt),
|
||||
"messages": _num(r"Messages:\s+([\d,]+)", txt),
|
||||
"tool_calls": _num(r"Tool calls:\s+([\d,]+)", txt),
|
||||
"total_tokens": _num(r"Total tokens:\s+([\d,]+)", txt),
|
||||
"active_time": _num(r"Active time:\s+(~?[\dhm ]+?)\s{2,}", txt),
|
||||
"top_tools": [],
|
||||
}
|
||||
# "Top Tools"-Sektion: Zeilen <name> <calls> <pct>%
|
||||
sec = txt.split("Top Tools", 1)
|
||||
if len(sec) == 2:
|
||||
for ln in sec[1].splitlines():
|
||||
m = re.match(r"\s*(\S.*?)\s+(\d+)\s+([\d.]+)%\s*$", ln)
|
||||
if m:
|
||||
out["top_tools"].append(
|
||||
{"name": m.group(1), "calls": int(m.group(2)), "pct": float(m.group(3))}
|
||||
)
|
||||
return out
|
||||
|
||||
return _cached("insights", _load) # type: ignore[return-value]
|
||||
|
||||
Reference in New Issue
Block a user