Compare commits

...
Author SHA1 Message Date
duffyduck ac40afc856 release(agent): bump to 0.0.1.1 2026-09-30 02:18:08 +02:00
duffyduckandClaude Opus 4.8 ff6d474f29 fix: Umbenennen greift nicht — Android lieferte den Intent nicht aus
Android vergleicht beim Activity-Start Komponente/Action/Daten, aber NICHT die
Extras. Ein zweites Provisioning galt damit als 'derselbe Intent': die Activity
wurde nur nach vorn geholt, onCreate/onNewIntent liefen nie — der alte Name blieb
stehen (und kam nach dem Loeschen aus der Liste sofort wieder).

- Satellit: eindeutige Daten-URI (aria://provision/<ms>) je Aufruf -> der Intent
  ist nie 'gleich'. Zusaetzlich wird die am-start-Ausgabe auf 'brought to the
  front'/'not started' geprueft und als Fehler gemeldet statt als Erfolg.
- Android: MainActivity launchMode=singleTop -> erneutes Provisioning landet
  zuverlaessig in onNewIntent (statt neuer Instanz); setIntent() ergaenzt.
Android-Version 0.0.1.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-30 02:16:21 +02:00
duffyduckandClaude Opus 4.8 44aef71813 fix: Leerzeichen im Geraetenamen + alte Agenten aus der Liste entfernbar
1) 'adb shell' haengt seine Argumente zu EINER Kommandozeile zusammen, die auf
   dem Geraet nochmal von der Shell zerlegt wird — '-e name Wohnzimmer Fire TV'
   kam als 'Wohnzimmer' an, der Rest fiel weg. Alle Werte werden jetzt mit
   shlex.quote gequotet.
2) Umbenennen erzeugt eine neue hostId, der alte Eintrag blieb fuer immer in der
   Diagnostic stehen (nach 5 min nur rot, nie weg). Neuer ✕-Button je Agent
   (host_forget) mit Rueckfrage; laeuft der Agent noch, meldet er sich von selbst
   wieder — das Entfernen stoppt ihn also nicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-30 02:07:27 +02:00
duffyduckandClaude Opus 4.8 27381e7038 feat(diagnostic): Geraetename beim Rollout am PC vergeben + 'Nur einrichten'
Fire TV meldet sich in der Netz-Erkennung oft nur mit der Seriennummer
(0d94400fa5b63301) — genau der Name, den man auf dem Fernseher nicht tippen will.

- Namensfeld im Rollout-Dialog (am PC getippt), wird beim Einrichten mitgegeben.
- Vorbelegung filtert technische Namen (Hex-Seriennummern, IPs) raus.
- Neuer Button 'Nur einrichten': setzt Zugang + Namen per adb.provision, OHNE die
  APK neu zu installieren — zum Umbenennen bereits ausgerollter Geraete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-30 02:01:55 +02:00
7 changed files with 86 additions and 11 deletions
+46 -1
View File
@@ -1624,8 +1624,19 @@
Spart das Abtippen auf dem Fernseher: Host und Token gehen direkt vom
Server an den Agenten — QR-Scan geht am Fire TV ja nicht.
</div>
<div style="margin-top:10px;margin-left:26px;">
<label style="display:block;margin-bottom:4px;color:#8888AA;">Name des Geräts</label>
<input id="apk-name" type="text" style="width:100%;padding:6px;background:#0D0D1A;border:1px solid #1E1E2E;border-radius:4px;color:#E0E0F0;"
placeholder="z.B. Wohnzimmer Fire TV">
<div style="color:#666;font-size:11px;margin-top:4px;">
So spricht ARIA das Gerät an („mach einen Screenshot vom Wohnzimmer Fire TV").
Hier am PC eingeben — auf dem Fernseher wäre das Tippen mühsam.
</div>
</div>
<div style="margin-top:14px;">
<button class="btn" onclick="runApkDeploy()">📦 Jetzt ausrollen</button>
<button class="btn secondary" onclick="runApkProvision()" style="margin-left:6px;"
title="Nur Zugang/Namen setzen — App bleibt wie sie ist">Nur einrichten</button>
<button class="btn secondary" onclick="runApkStatus()" style="margin-left:6px;">Status prüfen</button>
<span id="apk-status" style="margin-left:10px;color:#8888AA;"></span>
</div>
@@ -4716,11 +4727,22 @@
'<strong style="color:#E0E0F0;">💻 ' + escapeHtml(h.name || h.hostId) + '</strong> ' +
'<span style="color:#666;font-size:11px;">id=' + escapeHtml(h.hostId) + '</span></div>' +
'<button onclick="openAgentHistory(\'' + escapeHtml(h.hostId) + '\')" style="background:#12121E;color:#FFD60A;border:1px solid #FFD60A44;border-radius:6px;padding:4px 10px;font-size:12px;cursor:pointer;">🕘 Historie</button>' +
'<button onclick="forgetHost(\'' + escapeHtml(h.hostId) + '\',\'' + escapeHtml(h.name || h.hostId) + '\')" title="Aus der Liste entfernen (z.B. alter Name nach dem Umbenennen). Meldet sich der Agent wieder, erscheint er von selbst."' +
' style="background:#12121E;color:#8888AA;border:1px solid #1E1E2E;border-radius:6px;padding:4px 9px;font-size:12px;cursor:pointer;margin-left:4px;">✕</button>' +
'</div>' +
'<div style="color:#8888AA;font-size:11px;margin-top:4px;">' + escapeHtml(h.os || '') + ' · kann: ' + escapeHtml(caps) + ' · ' + ctrl + '</div>' +
'</div>';
}).join('');
}
/** Entfernt einen Agenten aus der Liste (z.B. den alten Namen nach dem
* Umbenennen). Läuft er noch, meldet er sich von selbst wieder. */
function forgetHost(hostId, name) {
if (!confirm('"' + name + '" aus der Liste entfernen?\n\n' +
'Das stoppt den Agenten NICHT — läuft er noch, erscheint er ' +
'beim nächsten Lebenszeichen wieder.')) return;
send({ action: 'host_forget', hostId });
}
// ── APK-Rollout auf Android/Fire TV (per Satellit + ADB) ─────────
let apkTarget = null; // {satId, ip, name}
let apkRequestId = null;
@@ -4731,6 +4753,11 @@
document.getElementById('apk-status').textContent = '';
const log = document.getElementById('apk-log');
log.style.display = 'none'; log.textContent = '';
// Namensfeld vorbelegen — aber nicht mit Seriennummern/IP-Platzhaltern,
// die die Netz-Erkennung bei Fire TV oft liefert (z.B. 0d94400fa5b63301).
const looksTechnical = !name || /^[0-9a-f]{8,}$/i.test(name) ||
/^\d{1,3}(\.\d{1,3}){3}$/.test(name);
document.getElementById('apk-name').value = looksTechnical ? '' : name;
document.getElementById('apk-modal').classList.add('open');
}
function closeApkDeploy() {
@@ -4755,12 +4782,30 @@
// Server ergaenzt Host/Port/Token — steht bewusst NICHT im Browser-Formular.
if (document.getElementById('apk-provision').checked) {
params.provision = true;
params.name = apkTarget.name;
// Am PC getippter Name gewinnt; sonst der aus der Netz-Erkennung (der
// ist bei Fire TV oft nur die Seriennummer).
params.name = document.getElementById('apk-name').value.trim() || apkTarget.name;
}
send({ action: 'sat_command', satellite: apkTarget.satId, cmd: 'adb.install',
requestId: apkRequestId, params });
}
/** Nur einrichten: RVS-Zugang + Namen setzen, ohne die APK neu zu installieren
* (z.B. um ein bereits ausgerolltes Gerät umzubenennen). */
function runApkProvision() {
if (!apkTarget) return;
apkRequestId = 'apk_' + Date.now() + '_' + Math.floor(Math.random() * 100000);
document.getElementById('apk-status').innerHTML = '<span style="color:#FFD60A;">richte ein …</span>';
const log = document.getElementById('apk-log');
log.style.display = 'block';
log.textContent = 'Übertrage Zugang und Namen an den Agenten …\n' +
'Die App startet dabei kurz auf dem Bildschirm — das ist normal.';
send({ action: 'sat_command', satellite: apkTarget.satId, cmd: 'adb.provision',
requestId: apkRequestId,
params: { ip: apkTarget.ip, provision: true,
name: document.getElementById('apk-name').value.trim() || apkTarget.name } });
}
/** Prüft, ob der Agent auf dem Gerät installiert ist und läuft. */
function runApkStatus() {
if (!apkTarget) return;
+8
View File
@@ -2812,6 +2812,14 @@ wss.on("connection", (ws) => {
} else if (msg.action === "host_list") {
// Browser will die aktuelle Host-Agenten-Liste.
ws.send(JSON.stringify({ type: "host_update", hosts: hostList() }));
} else if (msg.action === "host_forget") {
// Alten/umbenannten Agenten aus der Liste werfen. Meldet er sich wieder
// (host_hello), taucht er automatisch neu auf — das ist also gefahrlos.
const id = String(msg.hostId || "");
if (id && hosts.delete(id)) {
log("info", "host", `Agent '${id}' aus der Liste entfernt`);
broadcastHosts();
}
} else if (msg.action === "rooms_query") {
// Browser will die RVS-Raum-Diagnose — via persistente rvsWs anfragen,
// die Antwort (rooms_info) kommt auf derselben Verbindung zurueck.
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId 'de.hackersoft.ariaagent'
minSdk 26
targetSdk 33 // 33 vermeidet die Foreground-Service-Typ-Pflicht von 34
versionCode 100
versionName '0.0.1.0'
versionCode 101
versionName '0.0.1.1'
}
// Fester Signaturschlüssel: jeder Build signiert mit DEMSELBEN Key, damit
@@ -38,6 +38,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -214,7 +214,9 @@ class MainActivity : AppCompatActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null && applyProvisioning(intent)) return
if (intent == null) return
setIntent(intent)
applyProvisioning(intent)
}
/**
+1 -1
View File
@@ -162,7 +162,7 @@ OUT_MAX_CHARS_HARD = int(os.environ.get("OUT_MAX_CHARS_HARD", "200000") or "2000
FILE_MAX_BYTES = int(os.environ.get("FILE_MAX_BYTES", str(10 * 1024 * 1024)) or str(10 * 1024 * 1024))
# Version (wird von release_agent.sh beim Release gesetzt).
AGENT_VERSION = "0.0.1.0"
AGENT_VERSION = "0.0.1.1"
HEARTBEAT_SEC = 25
+25 -6
View File
@@ -1301,22 +1301,41 @@ def _do_adb_provision(params: dict) -> dict:
return {"ok": False, "error": f"'{package}' ist nicht installiert — "
"erst adb.install ausfuehren."}
# 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.
import shlex
q = shlex.quote
# Der Intent MUSS eindeutig sein: Android vergleicht beim Start Komponente,
# Action und Daten — NICHT die Extras. Ohne eigene Daten-URI gilt ein zweites
# Provisioning als "gleicher Intent", die Activity wird nur nach vorn geholt
# und onCreate/onNewIntent laufen nie -> der alte Name bliebe stehen.
stamp = str(int(time.time() * 1000))
args = ["-s", target, "shell", "am", "start",
"-n", f"{package}/.MainActivity",
"-a", f"{package}.PROVISION",
"-e", "host", rvs_host,
"-e", "token", rvs_token,
"-d", q(f"aria://provision/{stamp}"),
"-e", "host", q(rvs_host),
"-e", "token", q(rvs_token),
"--ei", "port", str(int(params.get("rvs_port") or 443)),
"--ez", "tls", "true" if _as_bool(params.get("rvs_tls"), True) else "false",
"--ez", "control", "true" if _as_bool(params.get("control"), True) else "false"]
if params.get("name"):
args += ["-e", "name", str(params["name"])]
args += ["-e", "name", q(str(params["name"]))]
if params.get("reply_mode"):
args += ["-e", "reply_mode", str(params["reply_mode"])]
args += ["-e", "reply_mode", q(str(params["reply_mode"]))]
rc, out, err = _adb_run(args, timeout=60)
if rc != 0 or "Error" in f"{out} {err}":
return {"ok": False, "error": (f"{out} {err}".strip() or f"exit {rc}")[:400]}
txt = f"{out} {err}".strip()
if rc != 0 or "Error" in txt:
return {"ok": False, "error": (txt or f"exit {rc}")[:400]}
# Android meldet das, wenn es den Intent als "schon offen" abtut und die
# Activity nur nach vorn holt — dann kaeme die neue Konfiguration NICHT an.
if "brought to the front" in txt.lower() or "not started" in txt.lower():
return {"ok": False, "error":
"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]}
return {"ok": True, "result": {
"target": target, "package": package,
"message": f"Agent eingerichtet (RVS {rvs_host}) und gestartet"}}