Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b7e205ede | ||
|
|
47cc1b7f08 | ||
|
|
f4b7148cf9 |
+47
-1
@@ -2200,6 +2200,11 @@ class Agent:
|
|||||||
# Events vom letzten Turn weglassen
|
# Events vom letzten Turn weglassen
|
||||||
self._pending_events = []
|
self._pending_events = []
|
||||||
|
|
||||||
|
# Trace fuer die Agent-Historie (Diagnostic gruppiert Host-Aktionen pro
|
||||||
|
# dieser einen User-Nachricht). Best-effort, nur Anzeige.
|
||||||
|
self._trace_id = os.urandom(6).hex()
|
||||||
|
self._trace_msg = user_message[:160]
|
||||||
|
|
||||||
# Projekt-Kontext pro Request statt aus globalem State
|
# Projekt-Kontext pro Request statt aus globalem State
|
||||||
active_project_id = (project_id or "").strip()
|
active_project_id = (project_id or "").strip()
|
||||||
active_project = projects_mod.get_project(active_project_id) if active_project_id else None
|
active_project = projects_mod.get_project(active_project_id) if active_project_id else None
|
||||||
@@ -2658,8 +2663,49 @@ class Agent:
|
|||||||
return f"FEHLER: Satellit/Bridge nicht erreichbar: {exc}"
|
return f"FEHLER: Satellit/Bridge nicht erreichbar: {exc}"
|
||||||
|
|
||||||
def _dispatch_host(self, name: str, arguments: dict) -> str:
|
def _dispatch_host(self, name: str, arguments: dict) -> str:
|
||||||
|
"""Wrapper um _dispatch_host_inner: fuehrt die Host-Aktion aus und schreibt
|
||||||
|
danach einen Historie-Eintrag an die Bridge (fuer die Diagnostic-Ansicht
|
||||||
|
'was wurde pro Nachricht auf diesem Agent gemacht')."""
|
||||||
|
text = self._dispatch_host_inner(name, arguments)
|
||||||
|
try:
|
||||||
|
self._emit_agent_history(name, arguments, text)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _emit_agent_history(self, name: str, arguments: dict, text: str) -> None:
|
||||||
|
"""Best-effort: ein Historie-Eintrag pro Host-Aktion an die Bridge."""
|
||||||
|
if name == "host_list":
|
||||||
|
return
|
||||||
|
host = (arguments.get("host") or "").strip()
|
||||||
|
if not host:
|
||||||
|
return
|
||||||
|
ok = not text.lstrip().upper().startswith("FEHLER")
|
||||||
|
m = re.search(r"/shared/uploads/\S+?\.(?:png|jpg|jpeg|webp)", text)
|
||||||
|
body = {
|
||||||
|
"host": host,
|
||||||
|
"trace_id": getattr(self, "_trace_id", ""),
|
||||||
|
"msg": getattr(self, "_trace_msg", ""),
|
||||||
|
"tool": name,
|
||||||
|
"action": name[len("host_"):] if name.startswith("host_") else name,
|
||||||
|
"args": {k: v for k, v in arguments.items() if k != "host"},
|
||||||
|
"ok": ok,
|
||||||
|
"summary": (text.strip().splitlines()[0] if text.strip() else "")[:200],
|
||||||
|
"file": m.group(0) if m else None,
|
||||||
|
"ts": int(time.time() * 1000),
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
data = json.dumps(body).encode("utf-8")
|
||||||
|
req = urllib.request.Request(f"{BRIDGE_URL}/internal/agent-history",
|
||||||
|
data=data, method="POST",
|
||||||
|
headers={"Content-Type": "application/json"})
|
||||||
|
urllib.request.urlopen(req, timeout=5).close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _dispatch_host_inner(self, name: str, arguments: dict) -> str:
|
||||||
"""host_list / host_exec / host_read / host_write / host_info /
|
"""host_list / host_exec / host_read / host_write / host_info /
|
||||||
host_screenshot — via Bridge (/internal/host*) → RVS → Host-Agent."""
|
host_screenshot / host_ui_* — via Bridge (/internal/host*) → RVS → Host-Agent."""
|
||||||
import base64 as _b64
|
import base64 as _b64
|
||||||
|
|
||||||
def _post(path: str, body: dict, timeout: float) -> dict:
|
def _post(path: str, body: dict, timeout: float) -> dict:
|
||||||
|
|||||||
@@ -3589,6 +3589,17 @@ class ARIABridge:
|
|||||||
self._hosts[hid]["last_seen"] = time.time()
|
self._hosts[hid]["last_seen"] = time.time()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
elif msg_type == "agent_history_query":
|
||||||
|
# Diagnostic fragt die gespeicherte Historie eines Agenten ab.
|
||||||
|
hid = (payload.get("host") or "").strip()
|
||||||
|
events = self._agent_history_read(hid) if hid else []
|
||||||
|
await self._send_to_rvs({
|
||||||
|
"type": "agent_history_list",
|
||||||
|
"payload": {"host": hid, "events": events},
|
||||||
|
"timestamp": int(time.time() * 1000),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
elif msg_type == "worker_hello":
|
elif msg_type == "worker_hello":
|
||||||
iid = (payload.get("instanceId") or "").strip()
|
iid = (payload.get("instanceId") or "").strip()
|
||||||
if iid:
|
if iid:
|
||||||
@@ -4580,6 +4591,17 @@ class ARIABridge:
|
|||||||
timeout=float(data.get("timeout") or 60.0),
|
timeout=float(data.get("timeout") or 60.0),
|
||||||
)
|
)
|
||||||
await _send_response(writer, 200, result)
|
await _send_response(writer, 200, result)
|
||||||
|
elif method == "POST" and path == "/internal/agent-history":
|
||||||
|
# Brain: ein Historie-Eintrag pro Host-Aktion (fuer die Diagnostic).
|
||||||
|
try:
|
||||||
|
data = json.loads(body.decode("utf-8", "ignore"))
|
||||||
|
except Exception as exc:
|
||||||
|
await _send_response(writer, 400, {"error": f"bad json: {exc}"})
|
||||||
|
return
|
||||||
|
self._agent_history_append(data)
|
||||||
|
await self._send_to_rvs({"type": "agent_history", "payload": data,
|
||||||
|
"timestamp": int(time.time() * 1000)})
|
||||||
|
await _send_response(writer, 200, {"ok": True})
|
||||||
elif method == "POST" and path == "/internal/satellite":
|
elif method == "POST" and path == "/internal/satellite":
|
||||||
# Brain-Tool: Discovery oder Command an einen Satelliten.
|
# Brain-Tool: Discovery oder Command an einen Satelliten.
|
||||||
# body: {op:'discover'|'command', satellite, device?, action?, params?}
|
# body: {op:'discover'|'command', satellite, device?, action?, params?}
|
||||||
@@ -4929,6 +4951,51 @@ class ARIABridge:
|
|||||||
"online": (now - h.get("last_seen", 0)) < 300,
|
"online": (now - h.get("last_seen", 0)) < 300,
|
||||||
} for h in self._hosts.values()]
|
} for h in self._hosts.values()]
|
||||||
|
|
||||||
|
_AGENT_HISTORY_DIR = "/shared/config/agent_history"
|
||||||
|
_AGENT_HISTORY_MAX = 200
|
||||||
|
|
||||||
|
def _agent_history_path(self, host: str) -> str:
|
||||||
|
safe = re.sub(r"[^a-zA-Z0-9_-]+", "-", host).strip("-") or "host"
|
||||||
|
return os.path.join(self._AGENT_HISTORY_DIR, f"{safe}.jsonl")
|
||||||
|
|
||||||
|
def _agent_history_append(self, entry: dict) -> None:
|
||||||
|
"""Haengt einen Historie-Eintrag an <hostId>.jsonl (Ringpuffer)."""
|
||||||
|
host = (entry.get("host") or "").strip()
|
||||||
|
if not host:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
os.makedirs(self._AGENT_HISTORY_DIR, exist_ok=True)
|
||||||
|
path = self._agent_history_path(host)
|
||||||
|
lines = []
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
lines = f.read().splitlines()
|
||||||
|
lines.append(json.dumps(entry, ensure_ascii=False))
|
||||||
|
lines = lines[-self._AGENT_HISTORY_MAX:]
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join(lines) + "\n")
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("[history] append fehlgeschlagen: %s", exc)
|
||||||
|
|
||||||
|
def _agent_history_read(self, host: str, limit: int = 200) -> list:
|
||||||
|
try:
|
||||||
|
path = self._agent_history_path(host)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return []
|
||||||
|
out = []
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
out.append(json.loads(line))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return out[-limit:]
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
async def _host_request(self, host: str = "", action: str = "",
|
async def _host_request(self, host: str = "", action: str = "",
|
||||||
params: Optional[dict] = None,
|
params: Optional[dict] = None,
|
||||||
timeout: float = 60.0) -> dict:
|
timeout: float = 60.0) -> dict:
|
||||||
|
|||||||
+106
-1
@@ -1594,6 +1594,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Agent-Historie-Modal (Befehle + Screenshots pro Nachricht) -->
|
||||||
|
<div class="modal-overlay" id="agent-history-modal">
|
||||||
|
<div class="modal-box" style="max-width:760px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 id="agent-history-title">Historie</h3>
|
||||||
|
<button class="modal-close" onclick="closeAgentHistory()">×</button>
|
||||||
|
</div>
|
||||||
|
<div style="padding:6px 16px;color:#8888AA;font-size:11px;">
|
||||||
|
Jede Zeile = eine Anfrage an ARIA. Aufklappen zeigt, was auf diesem Agenten dafür gemacht wurde.
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="agent-history-body" style="padding:10px 16px 16px;max-height:70vh;overflow-y:auto;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Memory-Modal (Neu + Editieren) -->
|
<!-- Memory-Modal (Neu + Editieren) -->
|
||||||
<div class="modal-overlay" id="memory-modal">
|
<div class="modal-overlay" id="memory-modal">
|
||||||
<div class="modal-box" style="max-width:640px;">
|
<div class="modal-box" style="max-width:640px;">
|
||||||
@@ -2185,6 +2199,19 @@
|
|||||||
}
|
}
|
||||||
if (msg.type === 'host_update') { hosts = msg.hosts || []; renderHosts(); return; }
|
if (msg.type === 'host_update') { hosts = msg.hosts || []; renderHosts(); return; }
|
||||||
if (msg.type === 'rooms_info') { rooms = (msg.payload && msg.payload.rooms) || []; renderRooms(); return; }
|
if (msg.type === 'rooms_info') { rooms = (msg.payload && msg.payload.rooms) || []; renderRooms(); return; }
|
||||||
|
if (msg.type === 'agent_history_list') {
|
||||||
|
if (agentHistoryHost && msg.payload && msg.payload.host === agentHistoryHost) {
|
||||||
|
renderAgentHistory(msg.payload.events || []);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (msg.type === 'agent_history') {
|
||||||
|
// Live: wenn das Modal fuer diesen Host offen ist, neu abfragen (einfach + robust).
|
||||||
|
if (agentHistoryHost && msg.payload && msg.payload.host === agentHistoryHost) {
|
||||||
|
send({ action: 'agent_history_query', host: agentHistoryHost });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (msg.type === 'sat_creds_list_result') {
|
if (msg.type === 'sat_creds_list_result') {
|
||||||
const p = msg.payload || msg;
|
const p = msg.payload || msg;
|
||||||
const sat = p.satellite || '';
|
const sat = p.satellite || '';
|
||||||
@@ -4643,13 +4670,91 @@
|
|||||||
const caps = (h.caps || []).join(', ') || '—';
|
const caps = (h.caps || []).join(', ') || '—';
|
||||||
const ctrl = h.control ? '<span style="color:#0096FF;">steuerbar</span>' : '<span style="color:#FF6E6E;">CONTROL_ENABLED=false</span>';
|
const ctrl = h.control ? '<span style="color:#0096FF;">steuerbar</span>' : '<span style="color:#FF6E6E;">CONTROL_ENABLED=false</span>';
|
||||||
return '<div style="border:1px solid #1E1E2E;border-radius:8px;padding:10px;margin-bottom:8px;">' +
|
return '<div style="border:1px solid #1E1E2E;border-radius:8px;padding:10px;margin-bottom:8px;">' +
|
||||||
'<div><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + dot + ';margin-right:6px;"></span>' +
|
'<div style="display:flex;align-items:center;gap:8px;">' +
|
||||||
|
'<div style="flex:1;"><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + dot + ';margin-right:6px;"></span>' +
|
||||||
'<strong style="color:#E0E0F0;">💻 ' + escapeHtml(h.name || h.hostId) + '</strong> ' +
|
'<strong style="color:#E0E0F0;">💻 ' + escapeHtml(h.name || h.hostId) + '</strong> ' +
|
||||||
'<span style="color:#666;font-size:11px;">id=' + escapeHtml(h.hostId) + '</span></div>' +
|
'<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>' +
|
||||||
|
'</div>' +
|
||||||
'<div style="color:#8888AA;font-size:11px;margin-top:4px;">' + escapeHtml(h.os || '') + ' · kann: ' + escapeHtml(caps) + ' · ' + ctrl + '</div>' +
|
'<div style="color:#8888AA;font-size:11px;margin-top:4px;">' + escapeHtml(h.os || '') + ' · kann: ' + escapeHtml(caps) + ' · ' + ctrl + '</div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
// ── Agent-Historie (Befehle + Screenshots pro Nachricht) ──────────
|
||||||
|
let agentHistoryHost = null;
|
||||||
|
|
||||||
|
function openAgentHistory(hostId) {
|
||||||
|
const h = (hosts || []).find(x => x.hostId === hostId);
|
||||||
|
agentHistoryHost = hostId;
|
||||||
|
document.getElementById('agent-history-title').textContent =
|
||||||
|
'Historie: ' + ((h && (h.name || h.hostId)) || hostId);
|
||||||
|
document.getElementById('agent-history-body').innerHTML =
|
||||||
|
'<span style="color:#8888AA;">lade …</span>';
|
||||||
|
document.getElementById('agent-history-modal').classList.add('open');
|
||||||
|
send({ action: 'agent_history_query', host: hostId });
|
||||||
|
}
|
||||||
|
function closeAgentHistory() {
|
||||||
|
document.getElementById('agent-history-modal').classList.remove('open');
|
||||||
|
agentHistoryHost = null;
|
||||||
|
}
|
||||||
|
function toggleHistGroup(gi) {
|
||||||
|
const el = document.getElementById('hist-group-' + gi);
|
||||||
|
const caret = document.getElementById('hist-caret-' + gi);
|
||||||
|
if (!el) return;
|
||||||
|
const open = el.style.display !== 'none';
|
||||||
|
el.style.display = open ? 'none' : 'block';
|
||||||
|
if (caret) caret.textContent = open ? '▶' : '▼';
|
||||||
|
}
|
||||||
|
function renderAgentHistory(events) {
|
||||||
|
const body = document.getElementById('agent-history-body');
|
||||||
|
if (!body) return;
|
||||||
|
if (!events || !events.length) {
|
||||||
|
body.innerHTML = '<span style="color:#8888AA;">Noch keine Aktionen für diesen Agenten aufgezeichnet. ' +
|
||||||
|
'Sobald ARIA etwas auf ihm tut (Screenshot, tippen, …), erscheint es hier.</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Nach trace_id gruppieren (eine Gruppe = eine User-Nachricht), Reihenfolge erhalten.
|
||||||
|
const groups = [], idx = {};
|
||||||
|
events.forEach(ev => {
|
||||||
|
const key = ev.trace_id || ('_' + ev.ts);
|
||||||
|
if (!(key in idx)) { idx[key] = groups.length; groups.push({ msg: ev.msg || '', ts: ev.ts, items: [] }); }
|
||||||
|
groups[idx[key]].items.push(ev);
|
||||||
|
});
|
||||||
|
groups.reverse(); // neueste Nachricht oben
|
||||||
|
body.innerHTML = groups.map((g, gi) => {
|
||||||
|
const t = new Date(g.ts).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
||||||
|
const bad = g.items.some(e => e.ok === false);
|
||||||
|
const dot = bad ? '#FF9500' : '#34C759';
|
||||||
|
const head = '<div onclick="toggleHistGroup(' + gi + ')" style="cursor:pointer;padding:8px 10px;background:#12121E;border-radius:6px;display:flex;align-items:center;gap:8px;">' +
|
||||||
|
'<span id="hist-caret-' + gi + '" style="color:#8888AA;">' + (gi === 0 ? '▼' : '▶') + '</span>' +
|
||||||
|
'<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + dot + ';"></span>' +
|
||||||
|
'<span style="color:#666;font-size:11px;">' + t + '</span>' +
|
||||||
|
'<span style="color:#E0E0F0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + escapeHtml(g.msg || '(ohne Text)') + '</span>' +
|
||||||
|
'<span style="color:#8888AA;font-size:11px;white-space:nowrap;">' + g.items.length + ' Aktion(en)</span></div>';
|
||||||
|
const rows = g.items.map(e => {
|
||||||
|
const good = e.ok !== false;
|
||||||
|
const ic = good ? '✓' : '✗';
|
||||||
|
const icColor = good ? '#34C759' : '#FF6E6E';
|
||||||
|
const args = (e.args && Object.keys(e.args).length)
|
||||||
|
? ' <span style="color:#666;font-size:11px;">' + escapeHtml(JSON.stringify(e.args)) + '</span>' : '';
|
||||||
|
let thumb = '';
|
||||||
|
if (e.file) {
|
||||||
|
const base = String(e.file).split('/').pop();
|
||||||
|
thumb = '<div style="margin-top:5px;"><img src="/uploads/' + encodeURIComponent(base) +
|
||||||
|
'" loading="lazy" style="max-width:180px;max-height:320px;border:1px solid #1E1E2E;border-radius:6px;cursor:pointer;" ' +
|
||||||
|
'onclick="window.open(this.src)" onerror="this.style.display=\'none\'"></div>';
|
||||||
|
}
|
||||||
|
return '<div style="padding:6px 10px 6px 26px;border-bottom:1px solid #15151F;">' +
|
||||||
|
'<span style="color:' + icColor + ';">' + ic + '</span> ' +
|
||||||
|
'<code style="color:#FFD60A;">' + escapeHtml(e.action || e.tool || '?') + '</code>' + args +
|
||||||
|
(e.summary ? '<div style="color:#8888AA;font-size:11px;margin-top:2px;">' + escapeHtml(e.summary) + '</div>' : '') +
|
||||||
|
thumb + '</div>';
|
||||||
|
}).join('');
|
||||||
|
return '<div style="margin-bottom:6px;">' + head +
|
||||||
|
'<div id="hist-group-' + gi + '" style="display:' + (gi === 0 ? 'block' : 'none') + ';">' + rows + '</div></div>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
function scanSatellite(id) {
|
function scanSatellite(id) {
|
||||||
satScanning = id;
|
satScanning = id;
|
||||||
renderSatellites();
|
renderSatellites();
|
||||||
|
|||||||
@@ -1098,6 +1098,10 @@ function connectRVS(forcePlain) {
|
|||||||
} else if (msg.type === "rooms_info") {
|
} else if (msg.type === "rooms_info") {
|
||||||
// Antwort des RVS auf rooms_query → an den Browser (Raum-Diagnose).
|
// Antwort des RVS auf rooms_query → an den Browser (Raum-Diagnose).
|
||||||
broadcast({ type: "rooms_info", payload: msg.payload || {} });
|
broadcast({ type: "rooms_info", payload: msg.payload || {} });
|
||||||
|
} else if (msg.type === "agent_history" || msg.type === "agent_history_list") {
|
||||||
|
// Live-Event (agent_history) bzw. Abfrage-Antwort (agent_history_list)
|
||||||
|
// der Bridge → an den Browser (Agent-Historie).
|
||||||
|
broadcast({ type: msg.type, payload: msg.payload || {} });
|
||||||
} else if (msg.type === "sat_devices") {
|
} else if (msg.type === "sat_devices") {
|
||||||
// Antwort eines Satelliten auf sat_discover → Geraeteliste an Browser.
|
// Antwort eines Satelliten auf sat_discover → Geraeteliste an Browser.
|
||||||
const p = msg.payload || {};
|
const p = msg.payload || {};
|
||||||
@@ -1944,6 +1948,19 @@ const server = http.createServer((req, res) => {
|
|||||||
"Expires": "0",
|
"Expires": "0",
|
||||||
});
|
});
|
||||||
res.end(fs.readFileSync(htmlPath, "utf-8"));
|
res.end(fs.readFileSync(htmlPath, "utf-8"));
|
||||||
|
} else if (req.url.startsWith("/uploads/")) {
|
||||||
|
// Screenshots aus der Agent-Historie (nur /shared/uploads, kein Traversal).
|
||||||
|
const base = path.basename(decodeURIComponent(req.url.slice("/uploads/".length)));
|
||||||
|
const file = path.join("/shared/uploads", base);
|
||||||
|
if (!file.startsWith("/shared/uploads/") || !fs.existsSync(file)) {
|
||||||
|
res.writeHead(404); res.end("not found"); return;
|
||||||
|
}
|
||||||
|
const ext = path.extname(file).toLowerCase();
|
||||||
|
const ct = ext === ".png" ? "image/png"
|
||||||
|
: (ext === ".jpg" || ext === ".jpeg") ? "image/jpeg"
|
||||||
|
: ext === ".webp" ? "image/webp" : "application/octet-stream";
|
||||||
|
res.writeHead(200, { "Content-Type": ct, "Cache-Control": "max-age=86400" });
|
||||||
|
res.end(fs.readFileSync(file));
|
||||||
} else if (req.url === "/api/state") {
|
} else if (req.url === "/api/state") {
|
||||||
res.writeHead(200, { "Content-Type": "application/json" });
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
res.end(JSON.stringify({ state, logs: logs.slice(-100) }));
|
res.end(JSON.stringify({ state, logs: logs.slice(-100) }));
|
||||||
@@ -2796,6 +2813,11 @@ wss.on("connection", (ws) => {
|
|||||||
// Browser will die RVS-Raum-Diagnose — via persistente rvsWs anfragen,
|
// Browser will die RVS-Raum-Diagnose — via persistente rvsWs anfragen,
|
||||||
// die Antwort (rooms_info) kommt auf derselben Verbindung zurueck.
|
// die Antwort (rooms_info) kommt auf derselben Verbindung zurueck.
|
||||||
sendToRVS_raw({ type: "rooms_query", payload: {}, timestamp: Date.now() });
|
sendToRVS_raw({ type: "rooms_query", payload: {}, timestamp: Date.now() });
|
||||||
|
} else if (msg.action === "agent_history_query") {
|
||||||
|
// Browser will die gespeicherte Historie eines Agenten — die Bridge
|
||||||
|
// antwortet mit agent_history_list (auf derselben rvsWs).
|
||||||
|
sendToRVS_raw({ type: "agent_history_query",
|
||||||
|
payload: { host: String(msg.host || "") }, timestamp: Date.now() });
|
||||||
} else if (msg.action === "worker_list") {
|
} else if (msg.action === "worker_list") {
|
||||||
// Browser will die aktuelle Compute-Flotte.
|
// Browser will die aktuelle Compute-Flotte.
|
||||||
ws.send(JSON.stringify({ type: "worker_update", workers: workerList() }));
|
ws.send(JSON.stringify({ type: "worker_update", workers: workerList() }));
|
||||||
|
|||||||
@@ -46,6 +46,13 @@ ARIA-Flow „E-Mail einrichten": `app_launch` (Mail-App) → `screenshot`/`ui_du
|
|||||||
(sehen, was da ist) → `ui_tap`/`ui_text` (durchklicken) → wieder `ui_dump` prüfen,
|
(sehen, was da ist) → `ui_tap`/`ui_text` (durchklicken) → wieder `ui_dump` prüfen,
|
||||||
bis fertig. Genau das agentische Muster wie beim Endian-Fix, nur mit Handy-UI.
|
bis fertig. Genau das agentische Muster wie beim Endian-Fix, nur mit Handy-UI.
|
||||||
|
|
||||||
|
> **⚠️ Gerät muss ENTSPERRT sein, damit ARIA es steuern kann.** Gesten und
|
||||||
|
> Systemtasten (`ui_tap`/`ui_text`/`ui_swipe`/`ui_key`/`app_launch`) greifen bei
|
||||||
|
> gesperrtem Bildschirm nicht — Android-Sicherheit. Der Agent erkennt das und
|
||||||
|
> meldet „Gerät ist gesperrt — bitte erst entsperren", statt still „ok" zu sagen.
|
||||||
|
> **Sehen** (`screenshot`, `ui_dump`, `info`) funktioniert dagegen auch bei
|
||||||
|
> gesperrtem Gerät.
|
||||||
|
|
||||||
## Dauerbetrieb — Foreground-Service vs. Push (FCM)
|
## Dauerbetrieb — Foreground-Service vs. Push (FCM)
|
||||||
|
|
||||||
Der Agent muss **immer erreichbar** sein, obwohl Android Hintergrundprozesse
|
Der Agent muss **immer erreichbar** sein, obwohl Android Hintergrundprozesse
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId 'de.hackersoft.ariaagent'
|
applicationId 'de.hackersoft.ariaagent'
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 33 // 33 vermeidet die Foreground-Service-Typ-Pflicht von 34
|
targetSdk 33 // 33 vermeidet die Foreground-Service-Typ-Pflicht von 34
|
||||||
versionCode 6
|
versionCode 7
|
||||||
versionName '0.0.0.6'
|
versionName '0.0.0.7'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fester Signaturschlüssel: jeder Build signiert mit DEMSELBEN Key, damit
|
// Fester Signaturschlüssel: jeder Build signiert mit DEMSELBEN Key, damit
|
||||||
|
|||||||
@@ -140,6 +140,11 @@ class RvsClient(
|
|||||||
action == "info" -> doInfo()
|
action == "info" -> doInfo()
|
||||||
action == "screenshot" -> doScreenshot()
|
action == "screenshot" -> doScreenshot()
|
||||||
action == "ui_dump" -> doUiDump()
|
action == "ui_dump" -> doUiDump()
|
||||||
|
// Steuern geht nur bei ENTSPERRTEM Gerät (Gesten/Tasten greifen sonst
|
||||||
|
// nicht — Android-Sicherheit). Sehen (screenshot/ui_dump) geht gesperrt.
|
||||||
|
action in CONTROL_ACTIONS && isLocked() ->
|
||||||
|
err("Gerät ist gesperrt — zum Steuern bitte erst entsperren. " +
|
||||||
|
"(Screenshot/Bildschirm lesen geht auch gesperrt.)")
|
||||||
action == "ui_tap" -> doUiTap(params)
|
action == "ui_tap" -> doUiTap(params)
|
||||||
action == "ui_text" -> doUiText(params)
|
action == "ui_text" -> doUiText(params)
|
||||||
action == "ui_swipe" -> doUiSwipe(params)
|
action == "ui_swipe" -> doUiSwipe(params)
|
||||||
@@ -158,6 +163,14 @@ class RvsClient(
|
|||||||
send(webSocket, "host_result", result)
|
send(webSocket, "host_result", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Steuer-Aktionen, die ein entsperrtes Gerät brauchen. */
|
||||||
|
private val CONTROL_ACTIONS = setOf("ui_tap", "ui_text", "ui_swipe", "ui_key", "app_launch")
|
||||||
|
|
||||||
|
private fun isLocked(): Boolean = try {
|
||||||
|
val km = appCtx.getSystemService(Context.KEYGUARD_SERVICE) as android.app.KeyguardManager
|
||||||
|
km.isKeyguardLocked
|
||||||
|
} catch (_: Exception) { false }
|
||||||
|
|
||||||
private fun err(m: String): JSONObject = JSONObject().put("ok", false).put("error", m)
|
private fun err(m: String): JSONObject = JSONObject().put("ok", false).put("error", m)
|
||||||
|
|
||||||
/** Bildschirmfoto — selber Vertrag wie der Desktop-Agent: {format,bytes,base64}. */
|
/** Bildschirmfoto — selber Vertrag wie der Desktop-Agent: {format,bytes,base64}. */
|
||||||
|
|||||||
@@ -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))
|
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).
|
# Version (wird von release_agent.sh beim Release gesetzt).
|
||||||
AGENT_VERSION = "0.0.0.6"
|
AGENT_VERSION = "0.0.0.7"
|
||||||
|
|
||||||
HEARTBEAT_SEC = 25
|
HEARTBEAT_SEC = 25
|
||||||
CAPS = ["exec", "read", "write", "info", "screenshot"]
|
CAPS = ["exec", "read", "write", "info", "screenshot"]
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ const ALLOWED_TYPES = new Set([
|
|||||||
// host_hello/host_ping und fuehrt host_command aus (exec/read/write/info/
|
// host_hello/host_ping und fuehrt host_command aus (exec/read/write/info/
|
||||||
// screenshot) -> host_result. ARIA steuert so Rechner auch hinter NAT.
|
// screenshot) -> host_result. ARIA steuert so Rechner auch hinter NAT.
|
||||||
"host_hello", "host_ping", "host_command", "host_result",
|
"host_hello", "host_ping", "host_command", "host_result",
|
||||||
|
// Agent-Historie: Brain schickt pro Host-Aktion ein agent_history-Event (via
|
||||||
|
// Bridge), Diagnostic fragt die gespeicherte Historie per agent_history_query
|
||||||
|
// ab, die Bridge antwortet mit agent_history_list. OHNE diese Typen verwirft
|
||||||
|
// der RVS sie still an der Allow-List und die Historie bleibt leer.
|
||||||
|
"agent_history", "agent_history_query", "agent_history_list",
|
||||||
// Raum-Diagnose: Diagnostic fragt die aktuellen RVS-Raeume ab (rooms_query),
|
// Raum-Diagnose: Diagnostic fragt die aktuellen RVS-Raeume ab (rooms_query),
|
||||||
// RVS antwortet direkt mit rooms_info (Fingerprint + Laenge + Client-Zahl je
|
// RVS antwortet direkt mit rooms_info (Fingerprint + Laenge + Client-Zahl je
|
||||||
// Raum). Deckt Token-Prefix-Kollisionen auf (2 Raeume, gleicher 8-Zeichen-Log).
|
// Raum). Deckt Token-Prefix-Kollisionen auf (2 Raeume, gleicher 8-Zeichen-Log).
|
||||||
|
|||||||
Reference in New Issue
Block a user