Files
Claude (Werkstatt PC) e1ca7f6a5e Ideen-Queue: natives Hermes-Kanban wird DIE Sammelstelle (Abloesung S2)
Entscheid nach Probe (Hermes-first): Dispatcher im Gateway, Specifier
(triage -> sauberer Auftrag), Profil-Routing und Artefakt-Tracking sind
nativ E2E bewiesen — kein Eigenbau. Auftragsbuch bleibt das Klick-Gate.

- backend services/ideen.py + routers/ideen.py: MC2-Tuer zur Queue
  (GET /api/ideen, POST /api/ideen -> kanban create --triage, Archiv)
- AuftragsbuchView: Ideen-Queue-Sektion (Eingabefeld + Status-Liste),
  dist frisch gebaut
- mcp_voice.py: idee_notieren (Lucys Sprech-Leitung, Prompt-Diaet: 1 Tool)
- chef-gutachter-feed.sh: Morgenlage bekommt kanban stats + Bewegung
- Bagatell-Pfad: bagatell-annahme.sh + mc2-bagatell.timer (04:10) —
  NUR .md-Anlagen/-AEnderungen, max 2/Nacht, gleicher Annahme-Runner
  mit Health-Gate + Auto-Revert, Chronik/Telegram-Meldung
- werkstatt-SOUL.md: Leitplanken des Kanban-Worker-Profils (propose-only,
  nie main, nie Live-Checkout) — deploy.sh zieht sie kuenftig nach

Box-live heute schon (ausserhalb Repo, mit Backups): toolsets+kanban,
platform_toolsets.telegram+kanban, SOUL.md-Ideen-Regel, Profil werkstatt
(model.default=coder), Gateway neu gestartet + E2E-Tuer-Test gruen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:02:50 +02:00

121 lines
5.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Mission Control 2.0 — schlanker Voice-MCP-Server (für Lucys Sprach-Lane).
Prompt-Diät (Faden 7): die api_server-Plattform (Lucy Desktop) bekommt per
platform_toolsets.api_server NUR diesen dünnen Server statt der vollen
mission-control-stack-Control-Plane (26 Tools, ~große Schemas). Enthalten ist,
was man einer gesprochenen Assistentin zuruft: Erinnerungen + „geht's der Box gut?".
CLI/Telegram/Werkstatt behalten den vollen Stack (mcp_mc.py) — hier NICHTS ergänzen,
was nicht in ein Sprachgespräch gehört.
Env: MC_URL (default http://127.0.0.1:9001).
"""
import os
import httpx
from mcp.server.fastmcp import FastMCP
MC_URL = os.environ.get("MC_URL", "http://127.0.0.1:9001").rstrip("/")
mcp = FastMCP("mission-control-voice")
def _get(path: str, **params):
r = httpx.get(f"{MC_URL}{path}", params=params, timeout=30)
r.raise_for_status()
return r.json()
def _post(path: str, data: dict):
r = httpx.post(f"{MC_URL}{path}", json=data, timeout=30)
r.raise_for_status()
return r.json()
@mcp.tool()
def reminder_create(text: str, when: str, repeat: str = "") -> str:
"""Legt eine Erinnerung an: zur angegebenen Zeit sagt die Box dem Commander den Text AKTIV
an (Lucy spricht + Telegram). NUR nutzen, wenn der Commander ausdrücklich erinnert werden
will ('erinner mich', 'sag mir um', 'jeden Morgen').
when = ISO-8601 mit Datum UND Uhrzeit in der Lokalzeit des Commanders, z.B. 2026-07-04T09:00.
Relative Angaben ('in 20 Minuten', 'morgen früh') selbst aus der aktuellen Zeit im Kontext
umrechnen. repeat: '' einmalig | daily (täglich) | weekdays (MoFr) | weekly (wöchentlich).
text = die Ansage selbst, kurz und direkt (ohne 'Erinnerung:' davor — das ergänzt die Box)."""
try:
item = _post("/api/reminders", {"text": text, "when": when, "repeat": repeat})["item"]
except httpx.HTTPStatusError as e:
return f"Fehlgeschlagen: {e.response.json().get('detail', e.response.text[:200])}"
rep = {"daily": " (täglich)", "weekdays": " (werktags)", "weekly": " (wöchentlich)"}.get(item["repeat"], "")
return f"Erinnerung #{item['id']} angelegt: {item['when_local']}{rep} — „{item['text']}"
@mcp.tool()
def reminder_list() -> str:
"""Zeigt alle anstehenden Erinnerungen/Routinen (Nummer, Zeit, Text). Vor dem Löschen
hiermit die Nummer ermitteln."""
items = _get("/api/reminders")["items"]
if not items:
return "Keine Erinnerungen angelegt."
rep = {"daily": " · täglich", "weekdays": " · werktags", "weekly": " · wöchentlich"}
return "\n".join(f"#{i['id']} · {i['when_local']}{rep.get(i['repeat'], '')}{i['text']}" for i in items)
@mcp.tool()
def reminder_delete(reminder_id: int) -> str:
"""Löscht eine Erinnerung/Routine endgültig (Nummer aus reminder_list). NUR auf
ausdrücklichen Wunsch des Commanders."""
try:
r = httpx.delete(f"{MC_URL}/api/reminders/{reminder_id}", timeout=30)
r.raise_for_status()
gone = r.json()["deleted"]
except httpx.HTTPStatusError as e:
return f"Fehlgeschlagen: {e.response.json().get('detail', e.response.text[:200])}"
return f"Erinnerung #{gone['id']} gelöscht ({gone['when_local']} — „{gone['text']}“)."
@mcp.tool()
def box_status() -> str:
"""Kurzer Gesundheits-Check der Box (KI-Server): Engine, Agent-Hirn, Kern-Dienste.
Nutze dies, wenn der Commander fragt, ob mit der Box/dem System alles in Ordnung ist."""
try:
h = _get("/api/health")
except Exception as exc:
return f"MC2 antwortet nicht ({exc}) — das Backend selbst könnte down sein."
brain = h.get("brain") or {}
lines = [
f"{'' if h.get('engine_reachable') else ''} Modell-Engine",
f"{'' if brain.get('ready') else ''} Agent-Hirn ({brain.get('model', '?')})",
]
try:
svcs = _get("/api/system/services").get("services", [])
lines += [f"{'' if s.get('ok') else ''} {s.get('name')}" for s in svcs]
except Exception:
lines.append("(Dienste-Liste nicht abrufbar)")
return "\n".join(lines)
@mcp.tool()
def idee_notieren(titel: str, notiz: str = "") -> str:
"""Legt eine Idee/einen Auftrag des Commanders in die Ideen-Queue der Box. Nutzen, sobald
der Commander eine Idee für später äußert ('wäre cool wenn …', 'bau mal irgendwann …',
'die Box soll …') — NICHT nur versprechen. Die Box arbeitet die Idee selbst aus und baut;
das Ergebnis erscheint als Karte im Auftragsbuch, dort entscheidet der Commander.
titel = kurze deutsche Überschrift; notiz = seine Worte plus nötiger Kontext."""
try:
r = _post("/api/ideen", {"titel": titel, "notiz": notiz})
except httpx.HTTPStatusError as e:
try:
detail = e.response.json().get("detail", "")
except Exception:
detail = e.response.text[:200]
return f"Fehlgeschlagen: {detail}"
except Exception as exc:
return f"Queue nicht erreichbar ({exc})."
return f"Idee ist in der Queue ({r.get('id', '?')}). Die Box arbeitet sie von allein ab."
if __name__ == "__main__":
mcp.run()