Companion Panel: Button für die Hermes Web-GUI #70

Closed
opened 2026-10-17 13:27:10 +00:00 by alf · 7 comments
Owner

Zweck

Im Header des Companion Panels gibt es die drei Agent-Buttons (Hermes, opencode, Claude Code), die
alle denselben Terminal-Prozess öffnen, dazu Index-Manager und System-Monitor. Es fehlt ein Zugang zur
Hermes-Web-GUI (hermes dashboard) — der Oberfläche für Hermes-Konfiguration, API-Keys, Sessions und
Skills. Es soll ein eigener Button im Header geben, der diese Web-GUI öffnet und den dafür nötigen
Dienst bei Bedarf selbst startet.

Spezifikation

Öffnen: in einem Iris-eigenen Fenster (WKWebView), nicht im System-Browser. Bei erneutem Klick wird
dasselbe Fenster fokussiert, nicht ein zweites geöffnet.

Dienststart: Iris prüft, ob sein eigener Web-GUI-Dienst läuft. Läuft keiner, wird gestartet:
hermes dashboard --isolated --no-open --port 0. Der Port wird aus der Sentinel-Zeile
HERMES_DASHBOARD_READY port=N gelesen, die Hermes auf fd 1 schreibt.

Status im Button: farbig (Hermes-Purple), wenn der Dienst läuft; sonst grau — wie der
System-Monitor-Button.

Rechtsklick-Menü: „Web-GUI-Dienst neu starten" und „Web-GUI-Dienst stoppen", deaktiviert wenn der
Dienst nicht läuft. Betrifft nur Iris' eigenen Dienst.

Sichtbarkeit: nur wenn Hermes installiert ist (wie die Agent-Buttons).

Rahmen, der die Bauform bestimmt (live geprüft)

  1. Der bestehende Dienst liefert keine Web-GUI. Der Onboarding-LaunchAgent
    (com.nousresearch.hermes-serve, hermes serve --host 0.0.0.0 --skip-build) ist headless:
    app.state.serves_spa = not headless (hermes_cli/web_server.py:1326). Live: GET http://127.0.0.1:9119/ → 302 /login. Ein hermes dashboard ohne --isolated verweigert sich
    gegen diesen Backend mit exit 78 („headless hermes serve backend with no dashboard UI"). Der Button
    braucht ein eigenes Backend, kein Anhängen an den LaunchAgent.
  2. Loopback ist login-frei. Das Auth-Gate greift nur bei non-loopback Bind
    (should_require_auth, hermes_cli/web_server.py:481). Live bestätigt: ein --isolated Dashboard
    auf loopback liefert das SPA ohne Login. Das gespeicherte Passwort
    (hermesRemoteServeUsername/hermesRemoteServePassword) wird dafür nicht gebraucht.
  3. --port 0 statt 9119, weil 9119 vom LaunchAgent belegt ist. Wenn der LaunchAgent läuft, laufen
    danach zwei Hermes-Webserver parallel (9119 headless + Iris' GUI-Backend) — gewollt, der LaunchAgent
    bleibt unberührt.
  4. Nicht hermes dashboard --stop für das Stoppen: das trifft auch den LaunchAgent, der sich als
    KeepAlive-Job selbst neu startet (live: gestopfter PID 813, 6 Sekunden später neu als 80825).
    Gestoppt wird per SIGTERM auf Iris' eigenen Prozess.

Beteiligte Dateien

  • Iris/HermesWebGUIManager.swift (neu) — dependency-free, damit das IrisClient-Target mitbaut
  • Iris/HermesWebGUIWindowController.swift (neu) — Singleton-NSWindow-Pattern wie
    VaultIndexManagerWindowController, Inhalt WKWebView wie HTMLPreviewWindowController
  • Iris/CompanionPanelView.swift — panelHeader
  • Iris/Localizable.xcstrings — neue Schlüssel mit englischer Übersetzung

Nicht enthalten

  • Kein Auto-Login im Fenster (loopback braucht keins)
  • Kein System-Browser, keine URL-Anzeige im Panel
## Zweck Im Header des Companion Panels gibt es die drei Agent-Buttons (Hermes, opencode, Claude Code), die alle denselben Terminal-Prozess öffnen, dazu Index-Manager und System-Monitor. Es fehlt ein Zugang zur Hermes-Web-GUI (`hermes dashboard`) — der Oberfläche für Hermes-Konfiguration, API-Keys, Sessions und Skills. Es soll ein eigener Button im Header geben, der diese Web-GUI öffnet und den dafür nötigen Dienst bei Bedarf selbst startet. ## Spezifikation **Öffnen:** in einem Iris-eigenen Fenster (WKWebView), nicht im System-Browser. Bei erneutem Klick wird dasselbe Fenster fokussiert, nicht ein zweites geöffnet. **Dienststart:** Iris prüft, ob sein eigener Web-GUI-Dienst läuft. Läuft keiner, wird gestartet: `hermes dashboard --isolated --no-open --port 0`. Der Port wird aus der Sentinel-Zeile `HERMES_DASHBOARD_READY port=N` gelesen, die Hermes auf fd 1 schreibt. **Status im Button:** farbig (Hermes-Purple), wenn der Dienst läuft; sonst grau — wie der System-Monitor-Button. **Rechtsklick-Menü:** „Web-GUI-Dienst neu starten" und „Web-GUI-Dienst stoppen", deaktiviert wenn der Dienst nicht läuft. Betrifft nur Iris' eigenen Dienst. **Sichtbarkeit:** nur wenn Hermes installiert ist (wie die Agent-Buttons). ## Rahmen, der die Bauform bestimmt (live geprüft) 1. **Der bestehende Dienst liefert keine Web-GUI.** Der Onboarding-LaunchAgent (`com.nousresearch.hermes-serve`, `hermes serve --host 0.0.0.0 --skip-build`) ist headless: `app.state.serves_spa = not headless` (`hermes_cli/web_server.py:1326`). Live: `GET http://127.0.0.1:9119/` → `302 /login`. Ein `hermes dashboard` ohne `--isolated` verweigert sich gegen diesen Backend mit exit 78 („headless `hermes serve` backend with no dashboard UI"). Der Button braucht ein eigenes Backend, kein Anhängen an den LaunchAgent. 2. **Loopback ist login-frei.** Das Auth-Gate greift nur bei non-loopback Bind (`should_require_auth`, `hermes_cli/web_server.py:481`). Live bestätigt: ein `--isolated` Dashboard auf loopback liefert das SPA ohne Login. Das gespeicherte Passwort (`hermesRemoteServeUsername`/`hermesRemoteServePassword`) wird dafür nicht gebraucht. 3. **`--port 0`** statt 9119, weil 9119 vom LaunchAgent belegt ist. Wenn der LaunchAgent läuft, laufen danach zwei Hermes-Webserver parallel (9119 headless + Iris' GUI-Backend) — gewollt, der LaunchAgent bleibt unberührt. 4. **Nicht `hermes dashboard --stop`** für das Stoppen: das trifft auch den LaunchAgent, der sich als `KeepAlive`-Job selbst neu startet (live: gestopfter PID 813, 6 Sekunden später neu als 80825). Gestoppt wird per SIGTERM auf Iris' eigenen Prozess. ## Beteiligte Dateien - `Iris/HermesWebGUIManager.swift` (neu) — dependency-free, damit das `IrisClient`-Target mitbaut - `Iris/HermesWebGUIWindowController.swift` (neu) — Singleton-NSWindow-Pattern wie `VaultIndexManagerWindowController`, Inhalt WKWebView wie `HTMLPreviewWindowController` - `Iris/CompanionPanelView.swift` — `panelHeader` - `Iris/Localizable.xcstrings` — neue Schlüssel mit englischer Übersetzung ## Nicht enthalten - Kein Auto-Login im Fenster (loopback braucht keins) - Kein System-Browser, keine URL-Anzeige im Panel
Author
Owner

PR #71 gemergt: feat(panel): Hermes Web-GUI-Button im Header (iris/Iris#70)
#71

Commits:

  • feat(panel): Hermes Web-GUI-Button im Header (iris/Iris#70)
PR #71 gemergt: feat(panel): Hermes Web-GUI-Button im Header (iris/Iris#70) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/71 Commits: - feat(panel): Hermes Web-GUI-Button im Header (iris/Iris#70)
Author
Owner

PR #72 gemergt: feat(panel): HermesWebGUIManager + Fenster-Controllee (iris/Iris#70)
#72

Commits:

  • feat(panel): HermesWebGUIManager + Fenster-Controllee (iris/Iris#70)
PR #72 gemergt: feat(panel): HermesWebGUIManager + Fenster-Controllee (iris/Iris#70) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/72 Commits: - feat(panel): HermesWebGUIManager + Fenster-Controllee (iris/Iris#70)
Author
Owner

PR #73 gemergt: fix(panel): Sentinel-Timeout 120 s und Prozess-Beenden ohne Port (iris/Iris#70)
#73

Commits:

  • fix(panel): Sentinel-Timeout 120 s und Prozess-Beenden ohne Port (iris/Iris#70)
PR #73 gemergt: fix(panel): Sentinel-Timeout 120 s und Prozess-Beenden ohne Port (iris/Iris#70) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/73 Commits: - fix(panel): Sentinel-Timeout 120 s und Prozess-Beenden ohne Port (iris/Iris#70)
Author
Owner

Beschreibung des fertigen Zustands

Anmerkung: Forgejo hat keine Anhang-API für Issues (die Anhang-Route antwortet mit 404), daher steht
der fertige Zustand hier als Kommentar.

Was jetzt existiert

Im Header des Companion Panels, zwischen dem Claude-Code-Button und Index-Manager, ein Button
(globe):

  • Klick — öffnet die Hermes Web-GUI in einem Iris-eigenen Fenster (WKWebView, 1000×700). Bei
    erneutem Klick wird dasselbe Fenster fokussiert, nicht ein zweites geöffnet. Läuft der Dienst noch
    nicht, startet Iris ihn vorher.
  • Farbe — Hermes-Purple, wenn Iris' Dienst läuft, sonst grau (wie der System-Monitor-Button).
  • Rechtsklick — „Web-GUI-Dienst neu starten" und „Web-GUI-Dienst stoppen", deaktiviert wenn der
    Dienst nicht läuft. Betrifft nur Iris' eigenen Dienst, nicht den LaunchAgent.
  • Sichtbarkeit — nur wenn Hermes installiert ist.

Dienst

hermes dashboard --isolated --no-open --port 0. Der Port wird aus der Sentinel-Zeile
HERMES_DASHBOARD_READY port=N gelesen, die Hermes auf fd 1 schreibt; BACKEND_PORT_IN_USE port=N ist
der Fehlerfall. --port 0, weil 9119 vom Onboarding-LaunchAgent belegt ist. Kein --skip-build, damit
Hermes die Dist-Freshness selbst prüft; das Sentinel-Timeout ist 120 s, weil ein npm-Build länger dauern
kann. Endet der Prozess vor dem Sentinel, wird er beendet, damit er nicht als Waise weiterläuft.

Login ist nicht nötig: das Auth-Gate greift nur bei non-loopback Bind, Iris' Backend ist loopback. Das
gespeicherte Passwort (hermesRemoteServeUsername/hermesRemoteServePassword) wird nicht verwendet.

Wenn der LaunchAgent läuft, laufen danach zwei Hermes-Webserver parallel — 9119 (headless, kein SPA) und
Iris' eigenes GUI-Backend. Gewollt, der LaunchAgent bleibt unberührt.

Dateien

  • Iris/HermesWebGUIManager.swift (neu) — dependency-free, damit IrisClient mitbaut, ohne im
    Exception-Set zu stehen. Hermes-Pfad wird pro Aufruf übergeben (AgentAvailability.hermesPath).
  • Iris/HermesWebGUIWindowController.swift (neu) — Singleton-NSWindow mit WKWebView, Pattern wie
    VaultIndexManagerWindowController. Kein Certificate-Pinning (loopback HTTP, kein selbst-signiertes TLS).
  • Iris/CompanionPanelView.swift — panelHeader.
  • Iris/Localizable.xcstrings — drei neue Schlüssel mit englischer Übersetzung.

Geprüft

  • Iris- und IrisClient-Target bauen beide.
  • Sentinel kommt über eine Pipe prompt an (stdout): HERMES_DASHBOARD_READY port=53120.
  • SIGTERM auf Iris' eigenen Prozess beendet ihn, der LaunchAgent auf 9119 bleibt.
  • --isolated auf loopback liefert SPA-HTML ohne Login.

Noch nicht geprüft: der Button selbst im laufenden Iris — die laufende App ist der Build vor der
Änderung. Klick, Fensterfokus und Rechtsklick-Menü erst nach einem Neustart von Iris.

Doku: obsidian-docs/Iris/plans/HERMES-WEB-GUI.md, Daily Log 2026-10-17.

## Beschreibung des fertigen Zustands **Anmerkung:** Forgejo hat keine Anhang-API für Issues (die Anhang-Route antwortet mit 404), daher steht der fertige Zustand hier als Kommentar. ### Was jetzt existiert Im Header des Companion Panels, zwischen dem Claude-Code-Button und Index-Manager, ein Button (`globe`): - **Klick** — öffnet die Hermes Web-GUI in einem Iris-eigenen Fenster (WKWebView, 1000×700). Bei erneutem Klick wird dasselbe Fenster fokussiert, nicht ein zweites geöffnet. Läuft der Dienst noch nicht, startet Iris ihn vorher. - **Farbe** — Hermes-Purple, wenn Iris' Dienst läuft, sonst grau (wie der System-Monitor-Button). - **Rechtsklick** — „Web-GUI-Dienst neu starten" und „Web-GUI-Dienst stoppen", deaktiviert wenn der Dienst nicht läuft. Betrifft nur Iris' eigenen Dienst, nicht den LaunchAgent. - **Sichtbarkeit** — nur wenn Hermes installiert ist. ### Dienst `hermes dashboard --isolated --no-open --port 0`. Der Port wird aus der Sentinel-Zeile `HERMES_DASHBOARD_READY port=N` gelesen, die Hermes auf fd 1 schreibt; `BACKEND_PORT_IN_USE port=N` ist der Fehlerfall. `--port 0`, weil 9119 vom Onboarding-LaunchAgent belegt ist. Kein `--skip-build`, damit Hermes die Dist-Freshness selbst prüft; das Sentinel-Timeout ist 120 s, weil ein npm-Build länger dauern kann. Endet der Prozess vor dem Sentinel, wird er beendet, damit er nicht als Waise weiterläuft. Login ist nicht nötig: das Auth-Gate greift nur bei non-loopback Bind, Iris' Backend ist loopback. Das gespeicherte Passwort (`hermesRemoteServeUsername`/`hermesRemoteServePassword`) wird nicht verwendet. Wenn der LaunchAgent läuft, laufen danach zwei Hermes-Webserver parallel — 9119 (headless, kein SPA) und Iris' eigenes GUI-Backend. Gewollt, der LaunchAgent bleibt unberührt. ### Dateien - `Iris/HermesWebGUIManager.swift` (neu) — dependency-free, damit `IrisClient` mitbaut, ohne im Exception-Set zu stehen. Hermes-Pfad wird pro Aufruf übergeben (`AgentAvailability.hermesPath`). - `Iris/HermesWebGUIWindowController.swift` (neu) — Singleton-NSWindow mit WKWebView, Pattern wie `VaultIndexManagerWindowController`. Kein Certificate-Pinning (loopback HTTP, kein selbst-signiertes TLS). - `Iris/CompanionPanelView.swift` — `panelHeader`. - `Iris/Localizable.xcstrings` — drei neue Schlüssel mit englischer Übersetzung. ### Geprüft - Iris- und IrisClient-Target bauen beide. - Sentinel kommt über eine Pipe prompt an (stdout): `HERMES_DASHBOARD_READY port=53120`. - SIGTERM auf Iris' eigenen Prozess beendet ihn, der LaunchAgent auf 9119 bleibt. - `--isolated` auf loopback liefert SPA-HTML ohne Login. **Noch nicht geprüft:** der Button selbst im laufenden Iris — die laufende App ist der Build vor der Änderung. Klick, Fensterfokus und Rechtsklick-Menü erst nach einem Neustart von Iris. Doku: `obsidian-docs/Iris/plans/HERMES-WEB-GUI.md`, Daily Log 2026-10-17.
Author
Owner

PR #71 — Button im Panel-Header

#71

Der Button selbst: globe-Icon im panelHeader, zwischen Claude Code und Index-Manager, im bestehenden
Stil (20×20, Circle-Background, pointerCursor(), headerButtonTooltip). Klick ruft
HermesWebGUIManager.openWebGUI(hermesPath:), Rechtsklick das Kontextmenü mit Stop und Neu starten.
Farbe Hermes-Purple wenn der Dienst läuft, sonst textTertiary. Sichtbar nur bei installiertem Hermes.
Dazu @ObservedObject auf den Manager im Panel und die drei neuen Localizable.xcstrings-Schlüssel.

Dieser PR enthält nur die Panel-Seite — die Manager- und Fenster-Dateien kamen in PR #72, weil ship
neue Dateien erst mit -a mitnimmt.

### PR #71 — Button im Panel-Header https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/71 Der Button selbst: `globe`-Icon im `panelHeader`, zwischen Claude Code und Index-Manager, im bestehenden Stil (20×20, Circle-Background, `pointerCursor()`, `headerButtonTooltip`). Klick ruft `HermesWebGUIManager.openWebGUI(hermesPath:)`, Rechtsklick das Kontextmenü mit Stop und Neu starten. Farbe Hermes-Purple wenn der Dienst läuft, sonst `textTertiary`. Sichtbar nur bei installiertem Hermes. Dazu `@ObservedObject` auf den Manager im Panel und die drei neuen `Localizable.xcstrings`-Schlüssel. Dieser PR enthält nur die Panel-Seite — die Manager- und Fenster-Dateien kamen in PR #72, weil `ship` neue Dateien erst mit `-a` mitnimmt.
Author
Owner

PR #72 — Manager und Fenster-Controllee

#72

HermesWebGUIManager.swift: startet hermes dashboard --isolated --no-open --port 0, liest den Port aus
der Sentinel-Zeile auf fd 1, hält den Prozess unsupervised (ein lebender Prozess ist der
Erfolgszustand), Stop und Restart per SIGTERM auf den eigenen PID. Bewusst dependency-free für das
IrisClient-Target.

HermesWebGUIWindowController.swift: Singleton-NSWindow mit WKWebView, fokussiert bei erneutem Klick
und lädt auf den neuen Port um, wenn der Dienst neu gestartet wurde. Kein Pinning, weil loopback HTTP.

### PR #72 — Manager und Fenster-Controllee https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/72 `HermesWebGUIManager.swift`: startet `hermes dashboard --isolated --no-open --port 0`, liest den Port aus der Sentinel-Zeile auf fd 1, hält den Prozess unsupervised (ein lebender Prozess ist der Erfolgszustand), Stop und Restart per SIGTERM auf den eigenen PID. Bewusst dependency-free für das `IrisClient`-Target. `HermesWebGUIWindowController.swift`: Singleton-NSWindow mit WKWebView, fokussiert bei erneutem Klick und lädt auf den neuen Port um, wenn der Dienst neu gestartet wurde. Kein Pinning, weil loopback HTTP.
Author
Owner

PR #73 — Sentinel-Timeout und Waisen-Prozess

#73

Zwei Korrekturen am Manager:

  1. 120 s statt 20 s. Ohne --skip-build prüft Hermes die Dist-Freshness und baut das Web-UI neu,
    wenn es veraltet ist — ein npm-Build dauert länger als 20 s. Im Test reichten 20 s nur, weil die Dist
    frisch war.
  2. Prozess beenden, wenn kein Port kommt. Endet der Prozess vor dem Sentinel (Auth-Gate,
    Build-Fehler), blieb er bisher als Waise laufen, während isRunning falsch herum entschied — der
    nächste Klick hätte einen zweiten Dienst gestartet.

Zusätzlich isRunning über runningPort abgeleitet statt über den Prozess: nur runningPort ist
@Published, und die Farbe soll erst dann umspringen, wenn der Dienst wirklich erreichbar ist, nicht
schon in der Sekunde des Prozessstarts.

### PR #73 — Sentinel-Timeout und Waisen-Prozess https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/73 Zwei Korrekturen am Manager: 1. **120 s statt 20 s.** Ohne `--skip-build` prüft Hermes die Dist-Freshness und baut das Web-UI neu, wenn es veraltet ist — ein npm-Build dauert länger als 20 s. Im Test reichten 20 s nur, weil die Dist frisch war. 2. **Prozess beenden, wenn kein Port kommt.** Endet der Prozess vor dem Sentinel (Auth-Gate, Build-Fehler), blieb er bisher als Waise laufen, während `isRunning` falsch herum entschied — der nächste Klick hätte einen zweiten Dienst gestartet. Zusätzlich `isRunning` über `runningPort` abgeleitet statt über den Prozess: nur `runningPort` ist `@Published`, und die Farbe soll erst dann umspringen, wenn der Dienst wirklich erreichbar ist, nicht schon in der Sekunde des Prozessstarts.
alf closed this issue 2026-10-21 23:26:26 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
iris/Iris#70
No description provided.