feat: Brain-Tools fuer Overlay/Sprache + Rechte automatisch per ADB erteilen
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 <noreply@anthropic.com>
This commit is contained in:
+66
-1
@@ -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",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
@@ -2864,6 +2925,9 @@ class Agent:
|
|||||||
"host_ui_swipe": ("ui_swipe", ["x1", "y1", "x2", "y2", "duration_ms"]),
|
"host_ui_swipe": ("ui_swipe", ["x1", "y1", "x2", "y2", "duration_ms"]),
|
||||||
"host_ui_key": ("ui_key", ["key"]),
|
"host_ui_key": ("ui_key", ["key"]),
|
||||||
"host_app_launch": ("app_launch", ["package", "query", "command"]),
|
"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:
|
if name in _UI_ACTIONS:
|
||||||
action, keys = _UI_ACTIONS[name]
|
action, keys = _UI_ACTIONS[name]
|
||||||
@@ -3674,7 +3738,8 @@ class Agent:
|
|||||||
if name in ("host_list", "host_exec", "host_read", "host_write",
|
if name in ("host_list", "host_exec", "host_read", "host_write",
|
||||||
"host_info", "host_screenshot", "host_ui_dump",
|
"host_info", "host_screenshot", "host_ui_dump",
|
||||||
"host_ui_tap", "host_ui_text", "host_ui_swipe",
|
"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)
|
return self._dispatch_host(name, arguments)
|
||||||
if name == "vm_register":
|
if name == "vm_register":
|
||||||
pid = (project_id or "").strip()
|
pid = (project_id or "").strip()
|
||||||
|
|||||||
@@ -48,9 +48,11 @@ CONTROL_ENABLED=true
|
|||||||
# adb.provision Agent FERN einrichten: RVS-Zugang uebergeben, statt ihn mit der
|
# adb.provision Agent FERN einrichten: RVS-Zugang uebergeben, statt ihn mit der
|
||||||
# Fernbedienung abzutippen (params: ip, rvs_host, rvs_token, ...)
|
# Fernbedienung abzutippen (params: ip, rvs_host, rvs_token, ...)
|
||||||
# adb.status laeuft/installiert der Agent auf dem Geraet? (params: ip)
|
# 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
|
# ACHTUNG: Diese Liste ueberschreibt den Code-Default. Wird eine neue Aktion
|
||||||
# ergaenzt, MUSS sie hier nachgetragen werden — sonst lehnt der Satellit sie ab.
|
# 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) ──────────────────────────────
|
# ─── ADB / Android-Rollout (optional) ──────────────────────────────
|
||||||
# Fire TV Sticks & Android-Geraete im LAN: APK ausrollen ohne Datei-Kopieren.
|
# Fire TV Sticks & Android-Geraete im LAN: APK ausrollen ohne Datei-Kopieren.
|
||||||
|
|||||||
+49
-2
@@ -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)
|
return await loop.run_in_executor(None, _do_adb_provision, params)
|
||||||
if action == "adb.status":
|
if action == "adb.status":
|
||||||
return await loop.run_in_executor(None, _do_adb_status, params)
|
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."}
|
return {"ok": False, "error": f"Aktion '{action}' nicht implementiert."}
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return {"ok": False, "error": f"{action} fehlgeschlagen: {exc}"}
|
return {"ok": False, "error": f"{action} fehlgeschlagen: {exc}"}
|
||||||
@@ -1267,6 +1269,42 @@ def _do_adb_status(params: dict) -> dict:
|
|||||||
"nicht installiert")}}
|
"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:
|
def _do_adb_provision(params: dict) -> dict:
|
||||||
"""Richtet den ARIA-Agent auf dem Geraet FERN ein (RVS-Zugang uebergeben).
|
"""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 — "
|
return {"ok": False, "error": f"'{package}' ist nicht installiert — "
|
||||||
"erst adb.install ausfuehren."}
|
"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,
|
# WICHTIG: 'adb shell' haengt die Argumente zu EINER Kommandozeile zusammen,
|
||||||
# die auf dem Geraet nochmal von der Shell zerlegt wird. Werte mit Leerzeichen
|
# die auf dem Geraet nochmal von der Shell zerlegt wird. Werte mit Leerzeichen
|
||||||
# ("Wohnzimmer Fire TV") wuerden dabei abgeschnitten -> jeden Wert quoten.
|
# ("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 "
|
"Android hat den Einrichtungs-Intent nicht ausgeliefert (Activity "
|
||||||
"war schon offen). Auf dem Geraet die Agent-App einmal schliessen "
|
"war schon offen). Auf dem Geraet die Agent-App einmal schliessen "
|
||||||
"und erneut 'Nur einrichten' druecken. Details: " + txt[:200]}
|
"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": {
|
return {"ok": True, "result": {
|
||||||
"target": target, "package": package,
|
"target": target, "package": package, "permissions": perms, "message": msg}}
|
||||||
"message": f"Agent eingerichtet (RVS {rvs_host}) und gestartet"}}
|
|
||||||
|
|
||||||
|
|
||||||
def _as_bool(v, default: bool) -> bool:
|
def _as_bool(v, default: bool) -> bool:
|
||||||
|
|||||||
Reference in New Issue
Block a user