Terminal im Server: Claude-Code-Sessions und Shell auf iPhone, iPad und Mac-Remote-Fenster #274
Labels
No labels
herkunft/alpha-feedback
status
blockiert
status
zu-testen
typ
bug
typ
docs
typ
enhancement
typ
feature
typ
idea
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
iris/Iris#274
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Warum
Auf dem iPhone öffnet „Fortsetzen“ in der Claude-Code-Sessions-Liste das Terminal auf dem Mac Studio (dem Server). Der Schalter in der Kopfleiste (Smartphone/Mac-Symbol,
openLocallyOnTap) soll das steuern: Zeigt er das Gerät, läuft das Terminal in der App; zeigt er den Mac, öffnet sich wie heute Terminal.app auf dem Server. Dazu braucht Iris ein Terminal, das der Server per Netz bereitstellt (Anregung: Alfs Projekt web-appliance-dashboard, dort ttyd + xterm.js).Entscheidungen (grill-me, 2026-10-31)
forkpty), Routen/terminal/*(öffnen, lesen per Long-Poll, schreiben, Größe ändern, schließen, Liste). Kein WebSocket, kein Plugin-Dienst, kein Proxy (die vorhandenen Proxys puffern und reichen keine Streams durch)./terminal/*mit 403 ohne Freigabe (Muster/screen/*,RegisteredDeviceStore). Eine Sitzung gehört dem Gerät, das sie öffnet.claude --resume <id>und neue Claude-Sessions im Arbeitsordner der Session, dazu eine freie Shell; beides hinter derselben Gerätefreigabe.Etappen
/terminal/*, Freigabe pro Gerät im Companion-Panel, Terminal-Seite (xterm.js); Test per curl und Browser. Wire-Protokoll (02-Wire-Protokoll.md).PR #275 gemergt: feat(terminal): TerminalHost — Pseudo-Konsolen mit Long-Poll und Gerätebindung (#274)
http://docker:3000/iris/Iris/pulls/275
Commits:
Stand: Terminal-Kern
TerminalHost(E1, Schritt 1)Geändert: Neue Datei
Iris/TerminalHost.swiftohne App-Abhängigkeiten (gehört durch die synchronisierte Gruppe automatisch auch zu IrisClient, kompiliert dort ohne weiteres): Pseudo-Konsole je Sitzung, Kindprozess perposix_spawnmit eigener Sitzung (POSIX_SPAWN_SETSID,POSIX_SPAWN_CLOEXEC_DEFAULT, Arbeitsordner peraddchdir), Ringpuffer der Ausgabe (1 MiB, absolute Offsets), wartende Leser (Long-Poll bis 25 s), Schreiben, Größe, Schließen (HUP, nach 2 s KILL), Liste je Gerät, Leerlauf-Ende (Standard 30 Minuten), beendete Sitzungen 5 Minuten les- und listbar, höchstens 8 laufende Sitzungen. Zugriff nur für die Geräte-ID, die die Sitzung geöffnet hat (leere ID = dieser Mac). Logging überLoggerohne Ein- und Ausgabe des Terminals. Test:scripts/test-terminal-host.sh.Befund beim Bau:
fork/forkptyim laufenden Server wäre unsicher (viele Threads).posix_spawnmit SETSID liefert ein echtes Steuerterminal (pszeigtSs+); macOS verwirft aber eine VOR dem Start gesetzte Fenstergröße, daher wird sie danach gesetzt (Größe0 0im ersten Versuch).Geprüft:
swiftcmit den Projekt-Flags (-swift-version 5 -default-isolation MainActor);scripts/test-terminal-host.sh: 35 Prüfungen mit echten Prozessen grün (Ein-/Ausgabe, Exit-Status, Steuerterminal, Größe 30x100 und Änderung per SIGWINCH, Umgebung und Arbeitsordner, Zugriff nur für das eigene Gerät, Long-Poll mit und ohne Wecken, Schließen, Leerlauf, Sitzungsgrenze, 1,5 MB Ausgabe mit Scrollback-Begrenzung, Eingabegrenze, Startfehler). Gegenprobe: absichtlich falsche Erwartungen lassen den Test scheitern (2 von 35).Nicht geprüft: In der App (Xcode-Build des Ziels Iris und IrisClient steht aus), Verhalten unter Last,
claudeals Kindprozess.Offen: Routen
/terminal/*und Geräte-Freigabe im Server, Claude-Start über das bestehende Skript, Companion-Panel, Terminal-Seite, Wire-Protokoll.PR #276 gemergt: feat(terminal): /terminal/* im Mac-Server, Freigabe pro Gerät, Terminal-Seite (#274)
http://docker:3000/iris/Iris/pulls/276
Commits:
Stand: Terminal im Mac-Server, Freigabe pro Gerät, Terminal-Seite (E1)
Geändert:
IrisRemoteServer): RoutenGET /terminal/access,POST /terminal/open,GET /terminal/list,GET /terminal/<id>/read(Long-Poll bis 20 s, Antwort per Keep-Alive),POST …/write,…/resize,…/close, dazu die statische SeiteGET /terminal/ui[/<datei>](nur eine feste Dateiliste, Content-Security-Policy). Alle außer Seite undaccessverlangen die Geräte-Freigabe (403 sonst; Loopback ausgenommen), die Seite registriert kein Gerät.stop()beendet alle Sitzungen.RegisteredRemoteDevice.isTerminalAllowed(Standard aus, ältere gespeicherte Geräte bleiben lesbar),RegisteredDeviceStore.allowTerminal/revokeTerminal; Entzug, Sperren und Löschen beenden die Sitzungen des Geräts (TerminalHost.closeAll(owner:), mit Test). Companion Panel: Terminal-Symbol in der Gerätezeile (grün = erlaubt) neben Bildschirmfreigabe.CompanionManager.currentAgentLaunchConfig()herausgezogen (vorher inline), neuesprepareClaudeCodeTerminalScript(…)schreibt dasselbe Start-Skript wie das Terminal-Fenster (Sitzungs-Proxy, Modell,--resume/--name), aber ohne abschließendesexec $SHELL; die Pseudo-Konsole führt es mit/bin/zsh -laus. Freie Shell:/bin/zsh -il. Eigene kleine Umgebung für die Kindprozesse (nicht die der App).Resources/iris-terminal.{html,js,css}, xterm.js 5.5.0 und Fit-Addon, MIT, mit Lizenztext): Long-Poll-Schleife, Eingabe gebündelt in Reihenfolge, Tastenleiste (Esc, Tab, Strg einrastend, Pfeile, Bild auf/ab, Pos1, Ende, Strg-C, | ~ / -), Größe folgt Fenster und Bildschirmtastatur, Trennen (iris-terminal://detach), Beenden mit zweiter Bestätigung, Theme über--iris-*live. Sitzung und Anmeldung im Fragment der Adresse, das sofort entfernt wird.Entwicklerdokumentation/02-Wire-Protokoll.md(separater PR im Wurzel-Repo).Geprüft:
scripts/test-terminal-host.sh: 38 Prüfungen mit echten Prozessen grün (inkl.closeAll(owner:)).Nicht geprüft:
X-Device-ID-Bindung, Keep-Alive, das Gate für die statische Seite,/terminal/openmitclaudeund dem Agent-LLM-Skript.claudeals Kindprozess (nursh/zshgetestet), das Verhalten bei vielen Sitzungen, IrisClient-Build (die neuen Dateien kommen über die synchronisierte Gruppe auch dorthin,IrisRemoteServernicht).Offen: Alf baut und startet die App lokal; dann prüfe ich Freigabe und Routen gegen den echten Server. Danach E2 (iPhone-Fenster, Link aus dem Claude-Sessions-Plugin).
PR #140 gemergt: docs(wire): Terminal /terminal/* im Mac-Server (iris/Iris#274)
http://docker:3000/alf/2ndbrain/pulls/140
Commits:
PR #277 gemergt: test(terminal): Prüfskript gegen den laufenden Server (#274)
http://docker:3000/iris/Iris/pulls/277
Commits:
Stand: Prüfung gegen den laufenden Server (E1)
Geprüft am echten, neu gebauten Server (Port 8765, ohne Token, von Loopback):
GET /terminal/ui/und alle fünf Dateien kommen mit richtigen Typen und den Größen der Quellen (11637, 3278, 289441, 5559, 1497 Bytes), mit Content-Security-Policy undCache-Control: no-store; unbekannte Dateien und Pfad-Tricks (..%2f,%2e%2e/, NUL) ergeben 404;GET /terminal/access,POST /terminal/open,GET /terminal/list,GET /terminal/<id>/readohne Token oder mit falschem Token ergeben 401; der Gerätespeicher bleibt beim Laden der Seite unverändert (kein Phantomgerät).Neu:
scripts/terminal-e2e.shprüft die geschützten Routen gegen den laufenden Server von der LAN-Adresse aus (Loopback ist von der Freigabe ausgenommen): 403 ohne Freigabe, Freigabe im Companion Panel, Shell mit Rechenergebnis und Größe, Größenänderung, Liste, 404 bei unbekannter Sitzung, neue Claude-Code-Session, Entzug beendet die Sitzungen (Zählung der Kindprozesse von Iris). Das Token übergibt der Nutzer per Umgebungsvariable, es geht nie an Claude.Nicht geprüft: Die geschützten Routen (Freigabe, Geräte-ID-Bindung,
openmit Claude Code) laufen erst mit dem Skript.PR #278 gemergt: fix(terminal): Prüfskript wartet per Abfrage, Bash-3.2-tauglich (#274)
http://docker:3000/iris/Iris/pulls/278
Commits:
Stand: Prüfskript repariert (erster Lauf des Nutzers)
Erster Lauf (Alf, Mac Studio, LAN-Adresse): Teil 1 bestand (4 von 4): ohne Freigabe antwortet der echte Server
accessmit 200 undallowed: false,openundlistmit 403. Teil 2 bis 3 schlugen fehl, weil das Skript nicht gewartet hat:readbekommt in der Claude-Code-Sitzung keine Eingabe und kehrte sofort zurück, das Terminal-Symbol war beim Prüfen noch aus (403). Das ist ein Fehler des Skripts, kein Befund am Server.Fehler im Skript (behoben): (1)
read-Pausen durch Abfrage vonGET /terminal/accessersetzt (wartet bis zu 3 Minuten auf Einschalten und Ausschalten, braucht keine Tastatur). (2)caseinnerhalb von$(…)bricht in der macOS-Bash 3.2 ab;bash -nmit einer neueren Bash hatte das nicht gezeigt, jetzt#!/bin/bashund Prüfung mit/bin/bash -n. (3) Falsch-positiv:grep -q ""bei leerer Sitzungs-ID wertete „Liste enthält die Sitzung“ als bestanden; jetzt nur mit nicht leerer ID. (4) Die Entzugsprüfung verlangt, dass vorher mindestens ein Kindprozess lief.Nicht geprüft: Der geschützte Teil (Freigabe, Shell, Claude-Session, Entzug) steht bis zum zweiten Lauf aus.
E1 am echten Server geprüft (Alf lief
scripts/terminal-e2e.shauf dem Mac Studio gegen die LAN-Adresse; 19 von 19 Prüfungen):GET /terminal/access200 mitallowed: false;POST /terminal/openundGET /terminal/list403.stty size; echo e2e-$((20+22)); pwdliefert30 100,e2e-42,/private/tmp; Größe ändern 200; Liste enthält die Sitzung; unbekannte Sitzung 404./tmpmit dem aktuellen Agent-LLM: Sitzung öffnet (200) und liefert Ausgabe (1012 Zeichen, Claude-Code-Oberfläche).openwieder 403, Lesen der alten Sitzung 403, beide offenen Sitzungen sind beendet (Kindprozesse von Iris 2 → 0).Beim ersten Lauf lagen Fehler im Prüfskript (kein Warten bei fehlender Tastatur,
casein$(…)in Bash 3.2, falsch-positive Prüfung), nicht im Server; behoben in PR #278.Nicht geprüft: Terminal-Seite gegen den echten Server in einer WebView;
--resumeeiner bestehenden Session; Gerätezeile im Companion Panel wurde bedient (Einschalten und Ausschalten wirkten), aber nicht angesehen.Weiter: E2 (iPhone-Fenster, Link aus der Claude-Sessions-Seite).
PR #279 gemergt: feat(terminal): Wischen zum Scrollen, Tastatur ausblenden (iris/Iris#274)
http://docker:3000/iris/Iris/pulls/279
Commits:
Stand: Terminal-Seite – Wischen und Tastatur ausblenden
Geändert (
iris-terminal.js/.css)Geprüft
node --check).Nicht geprüft
Hinweis: Die Seite liegt im Mac-Server; die Änderung wirkt nach Neubau der Mac-App.
PR #280 gemergt: fix(terminal): Wischen im Chat scrollt den Chat, in der Eingabe die Historie (iris/Iris#274)
http://docker:3000/iris/Iris/pulls/280
Commits:
Stand: Terminal-Seite – Wischen im Chat scrollt den Chat, in der Eingabe die Eingabe-Historie
Befund: In Claude Code (alternativer Puffer) gingen alle Wischbewegungen als Pfeil-hoch/-runter ans Programm und schalteten nur die früheren Prompts durch.
Geändert (
iris-terminal.js)modes.mouseTrackingMode); sonst als Bild-hoch/-runter je halber Bildschirmhöhe.Geprüft: Syntax (
node --check).Nicht geprüft: Alles auf dem iPhone; ob Claude Code bei dir die Maus abfragt (sonst greift der Bild-hoch/-runter-Weg); ob die Eingabe-Zone von −3/+2 Zeilen bei mehrzeiligen Prompts passt.
PR #281 gemergt: fix(terminal): Chat-Scrollen gebündelt und mit Auslaufen (iris/Iris#274)
http://docker:3000/iris/Iris/pulls/281
Commits:
Stand: Terminal-Seite – Scrollen im Chat flüssiger
Befund: Das Chat-Scrollen auf dem iPhone funktionierte, ruckelte aber (laut Alf). Vermutete Ursachen: jede Zeile als eigene, nacheinander abgearbeitete Anfrage; kein Auslaufen nach dem Loslassen.
Geändert (
iris-terminal.js)emitScroll/takeLines) vereinheitlicht.Geprüft: Syntax (
node --check).Nicht geprüft: Alles auf dem iPhone; ob die 60 ms und die Abbremsung sich gut anfühlen (Stellschrauben: Verzögerung in
queueWheel, Faktor0.04instartInertia).PR #282 gemergt: feat(terminal): Terminal im Mac-Remote-Fenster mit Rechner/Server-Schalter und Sitzungsliste (iris/Iris#274)
http://docker:3000/iris/Iris/pulls/282
Commits:
Stand: Etappe 3 – Terminal im Mac-Remote-Fenster (Iris/IrisClient)
Geändert
RemoteTerminal.swift:RemoteTerminalClient(access/open/list/close gegen/terminal/*über die gepinnte Sitzung),RemoteTerminalWindowController(ein Fenster je Sitzung mit der Terminal-Seite des Servers; „Trennen“ schließt es, die Sitzung läuft weiter),RemoteTerminalListView(offene Sitzungen: Öffnen, Beenden, „Neue Shell“).PluginPanelView): Statuszeile zeigt, sobald der Server das Terminal für dieses Gerät freigegeben hat, den Schalter Rechner/Server (laptopcomputer/server.rack, Standard Server = bisheriges Verhalten) und einen Terminal-Knopf für die Liste samt freier Shell.window.irisHost.terminalwie auf dem iPhone (available,target=device/mac);iris-terminal://open?...aus der Seite öffnet Claude Code im Fenster (nur Claude, Sitzungs-ID muss UUID sein).PinnedWebView:iris-terminal://-Adressen abfangen, Skript nach jedem Laden.RemoteServerClient.terminalPageURL.Geprüft
Nicht geprüft
PR #283 gemergt: docs(handbuch): Kapitel Terminal auf iPhone und Mac-Client (iris/Iris#274)
http://docker:3000/iris/Iris/pulls/283
Commits:
Stand: Handbuch-Kapitel „Terminal auf iPhone und Mac-Client“
Geändert
build_handbuch.py(30 Kapitel);BENUTZERHANDBUCH.md(Iris-Hilfe-Kontext) inIris/Resources/aktualisiert, die HTML-Site inHandbuch/beider Apps neu erzeugt (dort ungetrackt).Geprüft: Build ohne Fehler.
Nicht geprüft: Darstellung der Site in der App; das Kapitel beschreibt auch den Stand des Mac-Remote-Fensters, der noch nicht von Hand ausprobiert ist.
Offen: Linux-Server hat noch kein Terminal (steht im Kapitel).