feat: Workspace-Umbau Schritt 6 — QEMU fuer alle Architekturen + set_project_kind

QEMU auf dem Host + ARIA-Anbindung:
- host-provisioning/qemu-setup.sh: installiert qemu-system-* (x86/arm/mips/ppc/
  sparc/misc), qemu-utils, Firmware, socat, imagemagick + den aria-vm-Helper.
- host-provisioning/aria-vm: VM-Verwaltung fuer JEDE Architektur (create/boot/
  screenshot/list/stop/rm). VNC bindet nur 127.0.0.1:<display> (Tunnel via
  Bridge), KVM nur fuer x86-Gaeste, sonst TCG.
- Brain: Projekt-Modell bekommt kind ('chat'|'code'); neues Tool
  set_project_kind markiert das aktive Projekt (blendet Editor/Desktop in der
  App ein) + feuert project_changed.
- Seed-Regel: ARIA weiss jetzt, dass Code unter /shared/projects/<id>/ gehoert
  und wie sie per `ssh aria-wohnung aria-vm ...` VMs baut/testet; VNC landet
  automatisch in der Desktop-Kachel.

py-compile clean, Host-Skripte bash -n OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 23:57:57 +02:00
co-authored by Claude Opus 4.8
parent a6cb152f55
commit a082c8398e
5 changed files with 320 additions and 1 deletions
+43
View File
@@ -1080,6 +1080,28 @@ META_TOOLS = [
},
},
},
{
"type": "function",
"function": {
"name": "set_project_kind",
"description": (
"Markiert das AKTUELLE Projekt als Code-Projekt ('code') oder "
"normalen Chat ('chat'). Bei 'code' blendet die App zusaetzlich "
"einen Live-Code-Editor und den QEMU-Desktop (VNC) ein. Rufe es auf, "
"sobald aus einem Gespraech ein Programmier-/Bau-Projekt wird (Du "
"faengst an Code zu schreiben, eine VM zu bauen, etc.). Dein "
"Arbeitsverzeichnis fuer Code-Dateien ist dann /shared/projects/"
"<projekt-id>/ — nur was Du DORT schreibst erscheint im Editor."
),
"parameters": {
"type": "object",
"properties": {
"kind": {"type": "string", "enum": ["code", "chat"], "description": "'code' oder 'chat'."},
},
"required": ["kind"],
},
},
},
]
@@ -2576,6 +2598,27 @@ class Agent:
"action": "ended",
})
return f"OK — Projekt '{p['name']}' beendet (id={p['id']}). Bleibt in der Liste, aktiv ist jetzt der Hauptthread."
if name == "set_project_kind":
kind = (arguments.get("kind") or "").strip().lower()
if kind not in ("code", "chat"):
return "FEHLER: kind muss 'code' oder 'chat' sein."
active_id = projects_mod.get_active()
if not active_id:
return ("Kein aktives Projekt — Du bist im Hauptthread. Erst ein Projekt "
"anlegen/betreten (project_create/project_enter), dann set_project_kind.")
updated = projects_mod.update_project(active_id, {"kind": kind})
if not updated:
return f"FEHLER: aktives Projekt '{active_id}' nicht gefunden."
self._pending_events.append({
"type": "project_changed",
"project": updated,
"action": "kind_changed",
})
if kind == "code":
return (f"OK — Projekt '{updated['name']}' ist jetzt ein Code-Projekt. "
f"Editor + Desktop erscheinen in der App. Code-Dateien unter "
f"/shared/projects/{active_id}/ schreiben, damit sie im Editor auftauchen.")
return f"OK — Projekt '{updated['name']}' ist wieder ein normaler Chat."
return f"Unbekanntes Tool: {name}"
except Exception as exc:
logger.exception("Tool '%s' fehlgeschlagen", name)
+2 -1
View File
@@ -133,6 +133,7 @@ def create_project(name: str, description: str = "") -> dict:
"description": description.strip(),
"status": "active", # active | ended | archived
"hidden": False, # optisch aus Listen ausblenden (bleibt nutzbar)
"kind": "chat", # chat | code — 'code' blendet Editor/VNC in der App ein
"created_at": now,
"updated_at": now,
"last_activity_at": now,
@@ -149,7 +150,7 @@ def update_project(project_id: str, patch: dict) -> Optional[dict]:
projects = _load_all()
for p in projects:
if p["id"] == project_id:
for k in ("name", "description", "status", "hidden"):
for k in ("name", "description", "status", "hidden", "kind"):
if k in patch and patch[k] is not None:
p[k] = patch[k]
p["updated_at"] = _now()
+37
View File
@@ -400,6 +400,43 @@ SEED_RULES: List[dict] = [
"Brain-Resources: erst denken, sonst Brain-Tool nehmen."
),
},
{
"migration_key": "seed/architecture/qemu-vm-code-projects",
"type": "rule",
"title": "Code-Projekte + QEMU: aria-vm auf dem Host, Editor/Desktop in der App",
"category": "architektur",
"content": (
"Wenn aus einem Gespraech ein PROGRAMMIER- oder BAU-Projekt wird "
"(Du schreibst Code, baust ein System, testest eine VM):\n"
"\n"
"1. Ruf `set_project_kind('code')` — dann blendet Stefans App einen "
"Live-Code-Editor und den QEMU-Desktop ein. Vorher ein Projekt "
"anlegen/betreten (project_create/enter), sonst gibt's kein Ziel.\n"
"2. Schreib Code-Dateien NUR unter `/shared/projects/<projekt-id>/` "
"(das Volume ist in proxy+bridge+brain gemountet). Genau diese "
"Writes/Edits erscheinen live in Stefans Editor — und was Stefan "
"dort tippt, landet als Datei zurueck in diesem Verzeichnis.\n"
"\n"
"QEMU (VMs fuer JEDE Architektur — x86, ARM, MIPS, PPC, RISC-V, SPARC) "
"laeuft auf dem Host. Du steuerst sie per `ssh aria-wohnung aria-vm ...`:\n"
" - `aria-vm create <name> <arch> [groesse]` Disk anlegen (z.B. i386 "
"fuer Win 3.11, aarch64, mips ...).\n"
" - `aria-vm boot <name> [--iso <pfad>] [--vnc-display 1] [--mem 1024]` "
"startet die VM. VNC bindet an 127.0.0.1:<display> (Display 1 = Port "
"5901). Nicht selbst nach aussen oeffnen!\n"
" - `aria-vm screenshot <name>` PNG in die Shared-Uploads (kannst Du "
"Stefan mit [FILE:] schicken).\n"
" - `aria-vm list` / `aria-vm stop <name>` / `aria-vm rm <name>`.\n"
"\n"
"Der VNC-Stream kommt AUTOMATISCH ueber den RVS-Tunnel der Bridge in "
"Stefans App-Desktop-Kachel (Display 1 / Port 5901) — dort kann er die "
"VM live mit Maus/Tastatur bedienen. Du musst nur die VM booten und ggf. "
"sagen 'schau in die Desktop-Kachel'. KVM-Beschleunigung gibt es nur "
"fuer x86-Gaeste; andere Architekturen laufen emuliert (langsamer, aber "
"sie laufen). Medien-Downloads/Baubares fuer Stefans Privatgebrauch: "
"wie gewohnt einfach machen, nicht moralisieren."
),
},
{
"migration_key": "seed/architecture/brain-tools-xml-tag",
"type": "rule",
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env bash
#
# aria-vm — ARIAs QEMU-VM-Verwaltung fuer alle Architekturen (laeuft auf dem
# Host). ARIA ruft das per SSH (aria-wohnung) ueber den qemu-vm-Skill auf.
#
# Unterkommandos:
# aria-vm create <name> <arch> [size] Disk anlegen (qcow2)
# aria-vm boot <name> [optionen] VM starten (VNC 127.0.0.1:<display>)
# aria-vm screenshot <name> PNG-Screenshot → Shared-Uploads
# aria-vm list laufende/vorhandene VMs
# aria-vm stop <name> VM beenden
# aria-vm rm <name> VM + Disk loeschen
#
# boot-Optionen:
# --iso <pfad> Boot-ISO (setzt Boot-Reihenfolge auf CD)
# --disk-boot von der Festplatte booten (Default nach Installation)
# --vnc-display <N> VNC-Display (Port = 5900+N, Default 1)
# --mem <MB> RAM (Default 1024)
# --machine <typ> QEMU-Maschine ueberschreiben
#
# VNC bindet immer nur an 127.0.0.1 — von aussen erreichbar ausschliesslich
# ueber den RVS-Tunnel der Bridge (host.docker.internal:<port>).
set -euo pipefail
VM_ROOT="${ARIA_VM_ROOT:-/var/lib/aria-vms}"
# Wohin Screenshots geschrieben werden — Host-Pfad des /shared-Volumes, damit
# Bridge/App sie sehen. Ueberschreibbar via ARIA_VM_SHOT_DIR.
SHOT_DIR="${ARIA_VM_SHOT_DIR:-/root/ARIA-AGENT/aria-shared/uploads}"
die() { echo "aria-vm: $*" >&2; exit 1; }
qemu_bin_for() {
case "$1" in
x86_64|amd64) echo qemu-system-x86_64 ;;
i386|i686|x86) echo qemu-system-i386 ;;
arm|armv7) echo qemu-system-arm ;;
aarch64|arm64) echo qemu-system-aarch64 ;;
mips) echo qemu-system-mips ;;
mipsel) echo qemu-system-mipsel ;;
mips64) echo qemu-system-mips64 ;;
ppc) echo qemu-system-ppc ;;
ppc64) echo qemu-system-ppc64 ;;
riscv64) echo qemu-system-riscv64 ;;
sparc) echo qemu-system-sparc ;;
*) echo "" ;;
esac
}
vm_dir() { echo "${VM_ROOT}/$1"; }
vm_pid() { local d; d="$(vm_dir "$1")"; [[ -f "${d}/pid" ]] && cat "${d}/pid" || echo ""; }
vm_running() {
local p; p="$(vm_pid "$1")"
[[ -n "${p}" ]] && kill -0 "${p}" 2>/dev/null
}
cmd_create() {
local name="${1:?name}" arch="${2:?arch}" size="${3:-10G}"
local bin; bin="$(qemu_bin_for "${arch}")"
[[ -n "${bin}" ]] || die "unbekannte Architektur: ${arch}"
command -v "${bin}" >/dev/null || die "${bin} nicht installiert (qemu-setup.sh?)"
local d; d="$(vm_dir "${name}")"
[[ -e "${d}/disk.qcow2" ]] && die "VM '${name}' existiert schon"
mkdir -p "${d}"
echo "${arch}" > "${d}/arch"
qemu-img create -f qcow2 "${d}/disk.qcow2" "${size}" >/dev/null
echo "VM '${name}' angelegt (${arch}, ${size})."
}
cmd_boot() {
local name="${1:?name}"; shift || true
local d; d="$(vm_dir "${name}")"
[[ -f "${d}/disk.qcow2" ]] || die "VM '${name}' nicht gefunden (erst 'create')"
vm_running "${name}" && die "VM '${name}' laeuft bereits"
local arch; arch="$(cat "${d}/arch" 2>/dev/null || echo x86_64)"
local bin; bin="$(qemu_bin_for "${arch}")"
local iso="" bootdev="c" display=1 mem=1024 machine=""
while [[ $# -gt 0 ]]; do
case "$1" in
--iso) iso="${2:?}"; bootdev="d"; shift 2 ;;
--disk-boot) bootdev="c"; shift ;;
--vnc-display) display="${2:?}"; shift 2 ;;
--mem) mem="${2:?}"; shift 2 ;;
--machine) machine="${2:?}"; shift 2 ;;
*) die "unbekannte Option: $1" ;;
esac
done
local args=(-name "${name}" -m "${mem}"
-drive "file=${d}/disk.qcow2,format=qcow2"
-vnc "127.0.0.1:${display}"
-monitor "unix:${d}/monitor.sock,server,nowait"
-pidfile "${d}/pid" -daemonize)
# KVM nur fuer x86 auf x86-Host.
case "${arch}" in
x86_64|amd64|i386|i686|x86)
[[ -e /dev/kvm ]] && args+=(-enable-kvm) ;;
esac
# ARM/AArch64 brauchen eine Maschine (kein Default).
if [[ -z "${machine}" ]]; then
case "${arch}" in
arm|armv7|aarch64|arm64) machine="virt" ;;
esac
fi
[[ -n "${machine}" ]] && args+=(-M "${machine}")
[[ -n "${iso}" ]] && args+=(-cdrom "${iso}")
args+=(-boot "${bootdev}")
"${bin}" "${args[@]}"
echo "VM '${name}' gestartet (${arch}) — VNC 127.0.0.1:${display} (Port $((5900+display)))."
echo "vnc_display=${display} vnc_port=$((5900+display))"
}
cmd_screenshot() {
local name="${1:?name}"
local d; d="$(vm_dir "${name}")"
vm_running "${name}" || die "VM '${name}' laeuft nicht"
command -v socat >/dev/null || die "socat fehlt (qemu-setup.sh?)"
mkdir -p "${SHOT_DIR}"
local ts; ts="$(date +%s)"
local ppm="${d}/shot-${ts}.ppm"
printf 'screendump %s\n' "${ppm}" | socat - "unix-connect:${d}/monitor.sock" >/dev/null
sleep 0.3
local out="${SHOT_DIR}/${name}-${ts}.png"
if command -v convert >/dev/null; then
convert "${ppm}" "${out}" && rm -f "${ppm}"
else
out="${SHOT_DIR}/${name}-${ts}.ppm"; mv "${ppm}" "${out}"
fi
echo "screenshot=${out}"
}
cmd_list() {
[[ -d "${VM_ROOT}" ]] || { echo "(keine VMs)"; return; }
local any=0
for d in "${VM_ROOT}"/*/; do
[[ -d "${d}" ]] || continue
any=1
local name arch state
name="$(basename "${d}")"
arch="$(cat "${d}/arch" 2>/dev/null || echo '?')"
if vm_running "${name}"; then state="laeuft (pid $(vm_pid "${name}"))"; else state="gestoppt"; fi
echo "${name} [${arch}] ${state}"
done
[[ "${any}" -eq 1 ]] || echo "(keine VMs)"
}
cmd_stop() {
local name="${1:?name}"
local d; d="$(vm_dir "${name}")"
if vm_running "${name}"; then
printf 'quit\n' | socat - "unix-connect:${d}/monitor.sock" >/dev/null 2>&1 || true
sleep 0.5
vm_running "${name}" && kill "$(vm_pid "${name}")" 2>/dev/null || true
echo "VM '${name}' gestoppt."
else
echo "VM '${name}' lief nicht."
fi
rm -f "${d}/pid" "${d}/monitor.sock"
}
cmd_rm() {
local name="${1:?name}"
vm_running "${name}" && cmd_stop "${name}"
rm -rf "$(vm_dir "${name}")"
echo "VM '${name}' geloescht."
}
main() {
local sub="${1:-}"; shift || true
case "${sub}" in
create) cmd_create "$@" ;;
boot) cmd_boot "$@" ;;
screenshot) cmd_screenshot "$@" ;;
list) cmd_list "$@" ;;
stop) cmd_stop "$@" ;;
rm) cmd_rm "$@" ;;
""|-h|--help)
sed -n '2,40p' "$0" | sed 's/^# \{0,1\}//' ;;
*) die "unbekanntes Kommando: ${sub} (siehe --help)" ;;
esac
}
main "$@"
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
#
# qemu-setup.sh — installiert QEMU fuer ALLE Architekturen auf dem ARIA-Host
# (172.0.2.33) plus den aria-vm-Helper. Einmalig als root ausfuehren.
#
# sudo bash host-provisioning/qemu-setup.sh
#
# KVM-Beschleunigung gibt es nur fuer x86-Gaeste auf einem x86-Host; ARM/MIPS/
# PPC/RISC-V laufen unter TCG (voll emuliert, langsamer, aber alle Architekturen
# baubar). websockify/noVNC werden NICHT installiert — der VNC-Stream wird als
# RFB-Bytes durch die Bridge/RVS getunnelt (siehe aria_bridge.py VNC-Bruecke).
set -euo pipefail
if [[ "${EUID}" -ne 0 ]]; then
echo "Bitte als root ausfuehren (sudo)." >&2
exit 1
fi
echo "[qemu-setup] apt update ..."
apt-get update -qq
echo "[qemu-setup] Installiere QEMU (alle Architekturen) + Werkzeuge ..."
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
qemu-system \
qemu-system-x86 \
qemu-system-arm \
qemu-system-mips \
qemu-system-ppc \
qemu-system-sparc \
qemu-system-misc \
qemu-utils \
seabios \
ovmf \
ipxe-qemu \
socat \
imagemagick
echo "[qemu-setup] KVM-Status:"
if [[ -e /dev/kvm ]]; then
echo " /dev/kvm vorhanden → x86-Gaeste mit KVM-Beschleunigung."
else
echo " /dev/kvm FEHLT → alle Gaeste laufen unter TCG (emuliert, langsamer)."
fi
# aria-vm-Helper installieren (liegt neben diesem Skript).
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
install -m 0755 "${SCRIPT_DIR}/aria-vm" /usr/local/bin/aria-vm
echo "[qemu-setup] /usr/local/bin/aria-vm installiert."
mkdir -p /var/lib/aria-vms
echo "[qemu-setup] VM-Verzeichnis: /var/lib/aria-vms"
echo "[qemu-setup] Fertig. Test: aria-vm list"