From 586a2bf134f9643f7ec16daec355b80fd7781c55 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Wed, 30 Sep 2026 02:37:20 +0200 Subject: [PATCH] feat: Brain-Tools fuer Overlay/Sprache + Rechte automatisch per ADB erteilen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) ARIA hatte KEIN Werkzeug fuer die Einblendung — der Agent kann notify/speak/ volume laengst, aber im Brain fehlten die Tools. Sie hat deshalb improvisiert ('cmd notification post', gibt es auf Android 9 nicht) und ist gescheitert. Neu: host_notify (Overlay/Sprache, mode ueberschreibbar), host_speak, host_volume — mit dem ausdruecklichen Hinweis, es NICHT ueber adb zu versuchen. 2) _adb_grant_permissions: appops PROJECT_MEDIA (Screenshots ohne Dialog) + SYSTEM_ALERT_WINDOW (Overlay) + pm grant RECORD_AUDIO (Mikro) werden beim Einrichten automatisch gesetzt — kein Zusammenklicken mit der Fernbedienung mehr. Best effort; was scheitert, wird als 'manuell noetig' gemeldet. Neue Aktion adb.permissions setzt sie einzeln nach. Co-Authored-By: Claude Opus 4.8 --- aria-brain/agent.py | 67 +++++++++++++++++++++++++++++++++++++++++- satellite/.env.example | 4 ++- satellite/satellite.py | 51 ++++++++++++++++++++++++++++++-- 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/aria-brain/agent.py b/aria-brain/agent.py index 8c1234f..2c1284f 100644 --- a/aria-brain/agent.py +++ b/aria-brain/agent.py @@ -1501,6 +1501,67 @@ META_TOOLS = [ }, }, }, + { + "type": "function", + "function": { + "name": "host_notify", + "description": ( + "Zeigt eine Nachricht AUF einem Host-Agenten an — bei einem Fire TV / " + "Android TV als EINBLENDUNG ueber dem laufenden Bild (Netflix & Co. " + "laufen weiter), wahlweise als Sprachausgabe. NIMM DIESES TOOL, wenn " + "Stefan etwas 'auf dem Fernseher einblenden/anzeigen' will — versuche " + "das NICHT ueber host_exec/adb (dort gibt es kein passendes Kommando). " + "mode: 'overlay' (einblenden), 'speech' (vorlesen) oder 'both'; ohne " + "Angabe gilt die Einstellung am Geraet. Braucht dort einmalig die " + "Berechtigung 'Ueber anderen Apps anzeigen'." + ), + "parameters": { + "type": "object", + "properties": { + "host": {"type": "string", "description": "Host-ID/Name (z.B. 'Fire-TV Schlafzimmer')."}, + "text": {"type": "string", "description": "Der anzuzeigende Text."}, + "title": {"type": "string", "description": "Ueberschrift (Default 'ARIA')."}, + "seconds": {"type": "integer", "description": "Anzeigedauer (Default 12)."}, + "mode": {"type": "string", "description": "overlay | speech | both"}, + }, + "required": ["host", "text"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "host_speak", + "description": ( + "Laesst einen Host-Agenten Text laut vorlesen (Fire TV: ueber die " + "Fernseher-Lautsprecher). Fuer reine Sprachansagen; soll es auch/nur " + "sichtbar sein, nimm host_notify." + ), + "parameters": { + "type": "object", + "properties": { + "host": {"type": "string", "description": "Host-ID/Name."}, + "text": {"type": "string", "description": "Was gesprochen werden soll."}, + }, + "required": ["host", "text"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "host_volume", + "description": "Setzt die Medien-Lautstaerke eines Host-Agenten (Fire TV / Android).", + "parameters": { + "type": "object", + "properties": { + "host": {"type": "string", "description": "Host-ID/Name."}, + "percent": {"type": "integer", "description": "0-100."}, + }, + "required": ["host", "percent"], + }, + }, + }, { "type": "function", "function": { @@ -2864,6 +2925,9 @@ class Agent: "host_ui_swipe": ("ui_swipe", ["x1", "y1", "x2", "y2", "duration_ms"]), "host_ui_key": ("ui_key", ["key"]), "host_app_launch": ("app_launch", ["package", "query", "command"]), + "host_notify": ("notify", ["text", "title", "seconds", "mode"]), + "host_speak": ("speak", ["text"]), + "host_volume": ("volume", ["percent"]), } if name in _UI_ACTIONS: action, keys = _UI_ACTIONS[name] @@ -3674,7 +3738,8 @@ class Agent: if name in ("host_list", "host_exec", "host_read", "host_write", "host_info", "host_screenshot", "host_ui_dump", "host_ui_tap", "host_ui_text", "host_ui_swipe", - "host_ui_key", "host_app_launch"): + "host_ui_key", "host_app_launch", + "host_notify", "host_speak", "host_volume"): return self._dispatch_host(name, arguments) if name == "vm_register": pid = (project_id or "").strip() diff --git a/satellite/.env.example b/satellite/.env.example index d0f5bf4..fee1649 100644 --- a/satellite/.env.example +++ b/satellite/.env.example @@ -48,9 +48,11 @@ CONTROL_ENABLED=true # adb.provision Agent FERN einrichten: RVS-Zugang uebergeben, statt ihn mit der # Fernbedienung abzutippen (params: ip, rvs_host, rvs_token, ...) # adb.status laeuft/installiert der Agent auf dem Geraet? (params: ip) +# adb.permissions Sonderrechte per ADB erteilen (Bildschirmaufnahme/Overlay/ +# Mikro) — spart das Zusammenklicken mit der Fernbedienung # ACHTUNG: Diese Liste ueberschreibt den Code-Default. Wird eine neue Aktion # ergaenzt, MUSS sie hier nachgetragen werden — sonst lehnt der Satellit sie ab. -CONTROL_ALLOWLIST=dial.launch,wol,http.get,snmp.get,snmp.walk,snmp.printer,snmp.ports,snmp.info,fritzbox.info,fritzbox.hosts,ssh.exec,adb.devices,adb.connect,adb.install,adb.shell,adb.provision,adb.status +CONTROL_ALLOWLIST=dial.launch,wol,http.get,snmp.get,snmp.walk,snmp.printer,snmp.ports,snmp.info,fritzbox.info,fritzbox.hosts,ssh.exec,adb.devices,adb.connect,adb.install,adb.shell,adb.provision,adb.status,adb.permissions # ─── ADB / Android-Rollout (optional) ────────────────────────────── # Fire TV Sticks & Android-Geraete im LAN: APK ausrollen ohne Datei-Kopieren. diff --git a/satellite/satellite.py b/satellite/satellite.py index 7d00624..8cb3631 100644 --- a/satellite/satellite.py +++ b/satellite/satellite.py @@ -637,6 +637,8 @@ async def _control(action: str, params: dict, devices: list[dict]) -> dict: return await loop.run_in_executor(None, _do_adb_provision, params) if action == "adb.status": return await loop.run_in_executor(None, _do_adb_status, params) + if action == "adb.permissions": + return await loop.run_in_executor(None, _do_adb_permissions, params) return {"ok": False, "error": f"Aktion '{action}' nicht implementiert."} except Exception as exc: return {"ok": False, "error": f"{action} fehlgeschlagen: {exc}"} @@ -1267,6 +1269,42 @@ def _do_adb_status(params: dict) -> dict: "nicht installiert")}} +def _adb_grant_permissions(target: str, package: str) -> dict: + """Erteilt dem Agenten die Sonderrechte per ADB — das spart das Antippen mit + der Fernbedienung (auf einem Fire TV besonders muehsam): + PROJECT_MEDIA Bildschirmaufnahme ohne Nachfrage-Dialog (Screenshots) + SYSTEM_ALERT_WINDOW Einblendung ueber dem laufenden Bild (Overlay) + RECORD_AUDIO Mikrofon (Sprachdialog) + Best effort: was nicht geht, wird gemeldet, bricht aber nichts ab.""" + granted, failed = [], [] + ops = [("appops", ["appops", "set", package, "PROJECT_MEDIA", "allow"], "Bildschirmaufnahme"), + ("appops", ["appops", "set", package, "SYSTEM_ALERT_WINDOW", "allow"], "Einblendung")] + perms = [("android.permission.RECORD_AUDIO", "Mikrofon")] + for _, cmd, label in ops: + rc, out, err = _adb_run(["-s", target, "shell"] + cmd, timeout=30) + (granted if rc == 0 and "error" not in f"{out}{err}".lower() else failed).append(label) + for perm, label in perms: + rc, out, err = _adb_run(["-s", target, "shell", "pm", "grant", package, perm], timeout=30) + (granted if rc == 0 and "error" not in f"{out}{err}".lower() else failed).append(label) + return {"granted": granted, "failed": failed} + + +def _do_adb_permissions(params: dict) -> dict: + """Nur die Berechtigungen setzen (ohne Neuinstallation/Einrichtung).""" + target = _adb_target(params) + if not target: + return {"ok": False, "error": "ip/host erforderlich."} + package = (params.get("package") or AGENT_PACKAGE).strip() + ok, msg = _adb_connect(target) + if not ok: + return {"ok": False, "error": msg} + r = _adb_grant_permissions(target, package) + msg = "erteilt: " + (", ".join(r["granted"]) or "—") + if r["failed"]: + msg += " · nicht moeglich: " + ", ".join(r["failed"]) + return {"ok": True, "result": {"target": target, "message": msg, **r}} + + def _do_adb_provision(params: dict) -> dict: """Richtet den ARIA-Agent auf dem Geraet FERN ein (RVS-Zugang uebergeben). @@ -1301,6 +1339,11 @@ def _do_adb_provision(params: dict) -> dict: return {"ok": False, "error": f"'{package}' ist nicht installiert — " "erst adb.install ausfuehren."} + # Sonderrechte gleich mit erteilen (Bildschirm/Overlay/Mikro) — sonst muesste + # man sie am Fernseher mit der Fernbedienung zusammenklicken. + perms = ({"granted": [], "failed": []} if params.get("skip_permissions") + else _adb_grant_permissions(target, package)) + # WICHTIG: 'adb shell' haengt die Argumente zu EINER Kommandozeile zusammen, # die auf dem Geraet nochmal von der Shell zerlegt wird. Werte mit Leerzeichen # ("Wohnzimmer Fire TV") wuerden dabei abgeschnitten -> jeden Wert quoten. @@ -1336,9 +1379,13 @@ def _do_adb_provision(params: dict) -> dict: "Android hat den Einrichtungs-Intent nicht ausgeliefert (Activity " "war schon offen). Auf dem Geraet die Agent-App einmal schliessen " "und erneut 'Nur einrichten' druecken. Details: " + txt[:200]} + msg = f"Agent eingerichtet (RVS {rvs_host}) und gestartet" + if perms.get("granted"): + msg += " · Rechte erteilt: " + ", ".join(perms["granted"]) + if perms.get("failed"): + msg += " · manuell noetig: " + ", ".join(perms["failed"]) return {"ok": True, "result": { - "target": target, "package": package, - "message": f"Agent eingerichtet (RVS {rvs_host}) und gestartet"}} + "target": target, "package": package, "permissions": perms, "message": msg}} def _as_bool(v, default: bool) -> bool: