feat: GPS-Trigger-Kopplung + generatives Cockpit (aria_view)

GPS: near()/entered_near()/left_near()-Watcher schalten das Tracking automatisch an bzw. beim Loeschen des letzten wieder aus; gpsTracking.start() sichert jetzt die Background-Permission. Cockpit-Fundament: neues Brain-Tool present_view emittiert aria_view (Orb + Karten), Bridge/RVS leiten weiter, ariaView.ts haelt die Spec App-seitig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 01:21:43 +02:00
co-authored by Claude Opus 4.8
parent f5b22253b2
commit fd6ba73f59
5 changed files with 239 additions and 6 deletions
+104 -6
View File
@@ -627,10 +627,11 @@ META_TOOLS = [
"name": "request_location_tracking",
"description": (
"Bittet die App, das kontinuierliche GPS-Tracking zu aktivieren oder zu "
"deaktivieren. Default ist AUS (Akku-Schutz). Nutze das wenn du einen "
"GPS-basierten Watcher anlegst (z.B. `near(...)`), sonst hat die App "
"veraltete Position und der Watcher feuert nie. Auch wieder ausschalten "
"wenn der letzte GPS-Watcher geloescht wurde."
"deaktivieren. Default ist AUS (Akku-Schutz). HINWEIS: Beim Anlegen/Loeschen "
"eines Standort-Watchers (`near/entered_near/left_near`) schaltet das System "
"das Tracking bereits AUTOMATISCH mit an bzw. aus — dieses Tool brauchst du "
"dafuer nicht mehr. Nutze es nur fuer manuelle/explizite Faelle (z.B. Tracking "
"kurz einschalten ohne Watcher)."
),
"parameters": {
"type": "object",
@@ -642,6 +643,48 @@ META_TOOLS = [
},
},
},
{
"type": "function",
"function": {
"name": "present_view",
"description": (
"Komponiere eine RAEUMLICHE Ansicht, die auf ARIAs Flaeche materialisiert "
"(Orb + Karten). Nutze das, wenn eine visuelle Anordnung besser ist als reiner "
"Text — z.B. eine Akte/Datei zeigen (Bild links, Text rechts, Karte unten), "
"einen Vergleich, eine Liste, eine Karte mit Orten, oder Code. NICHT fuer "
"normale Gespraechsantworten. Die Karten erscheinen ZUSAETZLICH zu deiner "
"(kurzen) Sprachantwort — halte die Antwort dann knapp, das Visuelle traegt."
),
"parameters": {
"type": "object",
"properties": {
"cards": {
"type": "array",
"description": "Die Karten der Ansicht, in sinnvoller Anordnung.",
"items": {
"type": "object",
"properties": {
"type": {"type": "string", "enum": ["text", "image", "map", "code", "list"],
"description": "Kartentyp"},
"title": {"type": "string", "description": "Optionaler Kartentitel"},
"md": {"type": "string", "description": "text/list: Markdown-Inhalt"},
"src": {"type": "string", "description": "image: Bild-URL oder /shared-Pfad"},
"markers": {"type": "array", "items": {"type": "object"},
"description": "map: Liste [{lat, lon, label}]"},
"path": {"type": "string", "description": "code: Datei-Pfad im Projekt"},
"lang": {"type": "string", "description": "code: Sprache fuers Highlighting"},
},
"required": ["type"],
},
},
"orb": {"type": "string", "enum": ["idle", "speaking", "working"],
"description": "Orb-Zustand nach dem Rendern (default: speaking)"},
"title": {"type": "string", "description": "Optionaler Titel der ganzen Ansicht"},
},
"required": ["cards"],
},
},
},
{
"type": "function",
"function": {
@@ -1355,6 +1398,17 @@ def _skill_to_tool(s: dict) -> dict:
}
# Standort-Funktionen in Watcher-Conditions — brauchen aktives GPS-Tracking.
_LOCATION_FUNCS = ("near(", "entered_near(", "left_near(")
def _condition_needs_location(condition: str) -> bool:
"""True, wenn die Watcher-Condition eine Standort-Funktion nutzt und damit
laufende GPS-Updates der App braucht."""
c = condition or ""
return any(fn in c for fn in _LOCATION_FUNCS)
class Agent:
# Mindest-Score den ein Cold-Memory-Treffer haben muss um in den
# System-Prompt aufgenommen zu werden. Unter dieser Schwelle ist's
@@ -2372,13 +2426,41 @@ class Agent:
"trigger": {"name": t["name"], "type": "watcher",
"condition": t["condition"], "message": t["message"]},
})
return f"OK — Watcher '{t['name']}' angelegt: feuert wenn '{t['condition']}'."
# GPS-Kopplung: ein Standort-Watcher (near/entered_near/left_near)
# ist tot, wenn die App kein Tracking sendet. Frueher musste ARIA
# separat request_location_tracking aufrufen — wurde oft vergessen,
# dann feuerte der Trigger nie. Jetzt automatisch mit-anschalten.
extra = ""
if _condition_needs_location(t["condition"]):
self._pending_events.append({
"type": "location_tracking",
"on": True,
"reason": f"watcher:{t['name']}",
})
extra = " GPS-Tracking automatisch aktiviert."
return f"OK — Watcher '{t['name']}' angelegt: feuert wenn '{t['condition']}'.{extra}"
if name == "trigger_cancel":
try:
triggers_mod.delete(arguments["name"])
return f"OK — Trigger '{arguments['name']}' geloescht."
except ValueError as e:
return f"FEHLER: {e}"
# GPS-Kopplung (Gegenstueck): existiert kein Standort-Watcher mehr,
# Tracking wieder ausschalten (Akku schonen).
extra = ""
remaining = triggers_mod.list_triggers(active_only=False)
still_needs_gps = any(
r.get("type") == "watcher"
and _condition_needs_location(r.get("condition") or "")
for r in remaining
)
if not still_needs_gps:
self._pending_events.append({
"type": "location_tracking",
"on": False,
"reason": "no-location-watchers",
})
extra = " GPS-Tracking deaktiviert (kein Standort-Watcher mehr)."
return f"OK — Trigger '{arguments['name']}' geloescht.{extra}"
if name == "request_location_tracking":
on = bool(arguments.get("on", False))
reason = (arguments.get("reason") or "").strip()
@@ -2388,6 +2470,22 @@ class Agent:
"reason": reason,
})
return f"OK — Tracking-Request gesendet (on={on}). App wird in Kuerze umschalten."
if name == "present_view":
cards = arguments.get("cards") or []
if not isinstance(cards, list) or not cards:
return "FEHLER: present_view braucht mindestens eine Karte in 'cards'."
view = {
"cards": cards,
"orb": arguments.get("orb") or "speaking",
"title": arguments.get("title") or "",
}
self._pending_events.append({
"type": "aria_view",
"view": view,
"project_id": project_id or "",
})
return (f"OK — Ansicht mit {len(cards)} Karte(n) an die Flaeche geschickt "
f"(Kontext: {project_id or 'Hauptchat'}).")
if name == "trigger_list":
items = triggers_mod.list_triggers(active_only=False)
if not items: