Vorher hiess "Instanz wechseln": Der laufende Prozess faehrt sich herunter und startet sich mit einer anderen Instanz neu. Wer wechselte, beendete zwangslaeufig die Instanz, aus der heraus er es tat - und genau dabei hat sich die Anwendung am 2026-09-08 aufgehaengt (#529). Jetzt laufen alle, und es kommt eine nach vorn. Neu: POST /api/instanz/fenster, wieder ausschliesslich ueber den Socket. Die Instanz holt ihr Fenster SELBST nach vorn; der Manager kennt kein Fenstersystem und soll keines kennen. Kopflos laeuft die Bitte ins Leere, und das ist die richtige Antwort. Das letzte Wort hat der Fenstermanager - Windows laesst einen Hintergrundprozess den Vordergrund nicht einfach an sich reissen; der kurze Ausflug ueber Topmost half im Versuch, unter Linux entscheidet der Fenstermanager. Mehr als bitten kann eine Anwendung nicht. ShutdownSignal ist zu InstanceControl geworden: beenden und Fenster sind zwei Bitten desselben Absenders ueber denselben Weg. Beenden gilt einmal, das Fenster beliebig oft - jeder Wechsel auf diese Instanz ist einer. --managed schaltet den Wechsel im Instanzfenster ab. Der Manager gibt es jedem Instanzprozess mit; das Fenster graut seinen Instanz-Schalter aus und erklaert auf Nachfrage, wo gewechselt wird. Zwei Schloesser an derselben Tuer: Der Knopf ist aus, und der Weg dahinter prueft es noch einmal - denn hinter ihm wartet ein Selbst-Neustart. instanceWindows in manager.json, vorbelegt auf true: Jede Instanz behaelt ihr Fenster. Auf einer Maschine ohne Bildschirmsitzung gehoert false hin. ABNAHME an echten Fenstern: Instanz ueber den Manager gestartet, PID 18128, Fenstertitel "ClawdDotNet", Befehlszeile mit --managed. Fenster von aussen minimiert (IsIconic True), dann POST /instanzen/Instance-Test/fenster: IsIconic False, im Vordergrund - und PID unveraendert 18128. Nichts neu gestartet. Das ist die Abnahmebedingung dieser Stufe. Build ohne Warnungen, 780 Tests gruen, 8 uebersprungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
651 lines
35 KiB
Markdown
651 lines
35 KiB
Markdown
# Instanzmanager — Befund und Konzept
|
||
|
||
Warum mehrere Instanzen heute nicht unabhängig voneinander laufen, und wie es
|
||
stattdessen aussehen soll.
|
||
|
||
Stand: 2026-09-08. Anlass: Beim Instanzwechsel hat sich die Anwendung aufgehängt
|
||
und wurde von Windows beendet. Vorhaben: **#528**, zugehöriger Fehler: **#529**.
|
||
|
||
Die Entscheidungen in Abschnitt 2 sind getroffen. Was danach kommt, folgt aus
|
||
ihnen.
|
||
|
||
---
|
||
|
||
## 1 — Der Befund
|
||
|
||
### 1.1 Was passiert ist
|
||
|
||
Am 2026-09-08 wurde aus der laufenden Anwendung heraus eine zweite Instanz
|
||
angelegt und auf sie gewechselt:
|
||
|
||
| Zeit | Quelle | Eintrag |
|
||
|---|---|---|
|
||
| 17:26:48 | Dateisystem | `Instance-Test2` angelegt |
|
||
| 17:26:50 | `Lifetime.log` | `Application is shutting down...` |
|
||
| 17:26:55 | `Diagnostics.log` | Zwei Ereignisströme beendet, 33 s und 776 s Laufzeit |
|
||
| 17:27:45 | Windows-Ereignisprotokoll | `AppHangB1` — „hat aufgehört mit Windows zu interagieren und wurde geschlossen" |
|
||
|
||
**Die Anwendung wurde nicht beendet, sie wurde erschlagen.** Bemerkenswert ist,
|
||
was dabei *nicht* geschah: Zu diesem Prozess steht **keine einzige
|
||
Protokollzeile** in den Instanzprotokollen — der Schreibpuffer wurde nie geleert.
|
||
Keine Fehlermeldung, kein Bugtracker-Eintrag, kein Watchdog-Alarm. Der Vorfall
|
||
ist ausschließlich rekonstruierbar, weil das Betriebssystem ihn festgehalten hat.
|
||
|
||
**Das ist der Kern:** Meldeweg, Watchdog und Fehler-Stream leben *in* dem
|
||
Prozess, über dessen Zustand sie berichten sollen. Fällt er hart, fällt der
|
||
Meldeweg mit — genau in dem Fall, der gemeldet werden müsste.
|
||
|
||
### 1.2 Was heute geteilt ist, obwohl es getrennt sein müsste
|
||
|
||
| Geteilt | Wirkung | Beleg |
|
||
|---|---|---|
|
||
| **Ein Protokollverzeichnis** | Alle Instanzen schreiben in dieselben Dateien, ohne Kennzeichnung, welche spricht | `Startup.log` enthält abwechselnd Zeilen von `Test` und `Test2` |
|
||
| **Eine `Settings.json`** | Vier Stellen schreiben sie im laufenden Betrieb. Zwei Instanzen: die letzte gewinnt, stillschweigend | `LicenseGate`, `MainWindowViewModel`, `AgentsPageViewModel`, `SettingsPageViewModel` |
|
||
| **Ein Sicherungsverzeichnis** | Ein Ordner für alle Archive. Die Aufbewahrung räumt dort nach dem Präfix `backup_<Instanzname>_` auf — und ein Präfix ist keine Grenze: `backup_Test_2_….zip` fängt mit `backup_Test_` an, `Test` wirft es also weg | `AppSettings.BackupDirectory` |
|
||
| **Ein Sicherungs-Schalter** | Wer die tägliche Sicherung für eine Instanz einschaltet, schaltet sie für alle ein | `AppSettings.AutoBackupEnabled` |
|
||
| **Ein Web-Port als Vorgabe** | Jede Instanz bringt `webServerPort: 7001` mit; die zweite kann nicht binden | `InstanceConfig.WebServerPort` |
|
||
|
||
### 1.3 Der Wechsel kann gar nicht anders enden
|
||
|
||
Der Instanzmanager ist heute ein Fenster *innerhalb* einer laufenden Instanz.
|
||
Wechseln heißt deshalb: Der Prozess fährt sich selbst herunter, startet sich mit
|
||
`--instance` neu und beendet sich. Wer die Instanz wechselt, beendet zwangsläufig
|
||
die, aus der heraus er es tut.
|
||
|
||
---
|
||
|
||
## 2 — Die Entscheidungen
|
||
|
||
Getroffen am 2026-09-08.
|
||
|
||
### E-M1 — Der Weg zwischen Manager und Instanz
|
||
|
||
**Entschieden: HTTP über einen Unix-Domain-Socket je Instanz, unter Windows über
|
||
eine Named Pipe.** Der Socket liegt im Instanzverzeichnis
|
||
(`<Instanz>/manager.sock`), Rechte 0600.
|
||
|
||
Warum nicht TCP: Ports müssten verwaltet, vergeben und gegen Kollisionen
|
||
gesichert werden — genau das Problem, an dem die heutige Vorbelegung `7001`
|
||
scheitert. Ein Socket im Instanzverzeichnis braucht keine Vergabe, und die
|
||
Zugriffsrechte stehen schon am Dateisystem.
|
||
|
||
Warum trotzdem HTTP darüber: Weil der Manager Anfragen an die Instanzen
|
||
**weiterleitet** (E-M5). Bleibt das Protokoll HTTP, ist das Weiterleiten eine
|
||
Weiterleitung und keine Übersetzung — und die bestehenden Endpunkte, DTOs und
|
||
der Ereignisstrom aus #494 gelten unverändert weiter. Kestrel kann auf beidem
|
||
hören, `SocketsHttpHandler` kann auf beidem verbinden.
|
||
|
||
### E-M2 — Die Oberflächen
|
||
|
||
**Entschieden: Jede Instanz behält ihr eigenes Desktop-Fenster, der Manager
|
||
bekommt ein eigenes.** Die Instanzfenster laufen im Hintergrund und werden beim
|
||
Wechsel nach vorn geholt. Ein Wechsel ist damit kein Neustart mehr, sondern ein
|
||
Fensterwechsel.
|
||
|
||
### E-M3 — Lizenz
|
||
|
||
**Entschieden: Die Lizenz liegt im Manager und wird dort geprüft.**
|
||
Instanzprozesse starten nicht ohne aktiven Manager. Eine Lizenz erlaubt beliebig
|
||
viele Instanzen.
|
||
|
||
Folge: `LicenseGate` und `LicenseWatch` wandern aus dem Instanzprozess in den
|
||
Manager. `AppHost.Callbacks.License` entfällt für Instanzen.
|
||
|
||
### E-M4 — Einstellungen
|
||
|
||
**Entschieden: Der Manager kennt nur das Nötigste, alles andere gehört der
|
||
Instanz.** Die Aufteilung steht in Abschnitt 6.
|
||
|
||
### E-M5 — Die Weboberfläche
|
||
|
||
**Entschieden: Eine Adresse, ein Zugang, alle Instanzen.** Der Manager besitzt
|
||
Port, Zugang und Rahmen. Anfragen zu Chat, Agenten, Aufgaben und Freigaben
|
||
reicht er über E-M1 an die betroffene Instanz weiter.
|
||
|
||
**Er leitet weiter, er implementiert nicht.** Keine Fachlogik im Manager — die
|
||
Weiterleitung kennt Kennungen und Pfade, sonst nichts.
|
||
|
||
### E-M6 — Wenn der Manager ausfällt
|
||
|
||
**Entschieden: Laufende Instanzen laufen weiter.** Der neu gestartete Manager
|
||
findet sie über eine Datei im Instanzverzeichnis wieder und übernimmt die
|
||
Aufsicht.
|
||
|
||
Damit wird der Manager nicht zu dem Einzelpunkt, gegen den dieses ganze Vorhaben
|
||
gebaut wird. Die Prüfung „gibt es einen Manager?" findet **beim Start** einer
|
||
Instanz statt, nicht fortlaufend.
|
||
|
||
Ausnahme: Ein **Lizenzwiderruf** ist kein Ausfall, sondern ein Urteil. Erfährt
|
||
der Manager davon, hält er die Instanzen an.
|
||
|
||
---
|
||
|
||
## 3 — Der Schnitt
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ Instanzmanager (ein Prozess je Maschine) │
|
||
│ · eigenes Fenster + die eine Weboberfläche │
|
||
│ · Instanzen anlegen, starten, anhalten, entfernen │
|
||
│ · Aufsicht: lebt sie? stürzt sie wiederholt ab? │
|
||
│ · Lizenz · Update · DC-Master-Token → Sub-Token je Instanz │
|
||
│ · KEINE Datenbank. KEINE Fachlogik. KEINE Werkzeuge. │
|
||
└──────┬───────────────────────────────────────────────────────┘
|
||
│ manager.sock (HTTP über UDS / Named Pipe)
|
||
┌───┴────────┬────────────────┬───────────────┐
|
||
▼ ▼ ▼ ▼
|
||
┌────────┐ ┌────────┐ ┌────────┐ Fenster je Instanz
|
||
│Instanz │ │Instanz │ │Instanz │ (laufen im Hinter-
|
||
│ A │ │ B │ │ C │ grund, kommen beim
|
||
│ │ │ │ │ │ Wechsel nach vorn)
|
||
│ Engine · Werkzeuge · Taskboard · Staging · state.db │
|
||
│ eigenes Protokoll · eigenes Sub-Token · eigener Meldeweg │
|
||
│ Telegram · Rocket.Chat · Mail · FTP — ausschliesslich hier │
|
||
└────────┘ └────────┘ └────────┘
|
||
```
|
||
|
||
### 3.1 Der Manager hat keine Datenbank
|
||
|
||
Alles, was er braucht, liegt als JSON im Projektverzeichnis: die Instanzliste,
|
||
seine drei Einstellungen, je Instanz eine kleine Zustandsdatei (läuft sie? seit
|
||
wann? mit welcher Prozesskennung? wie oft zuletzt abgestürzt?).
|
||
|
||
`state.db` einer Instanz fasst er **nicht** an — die Datei gehört genau einem
|
||
Prozess, und prozessübergreifend gilt die Schreibsperre von `SqliteStorage`
|
||
nicht.
|
||
|
||
### 3.2 Werkzeuge laufen ausschließlich in der Instanz
|
||
|
||
Telegram, Rocket.Chat, Mail, FTP, Datenbank — alle Werkzeuge gehören der Instanz
|
||
und nur ihr. Der Manager kennt sie nicht und spricht nie mit ihnen.
|
||
|
||
**Das gilt auch für die Telegram-Anmeldung.** Sie ist ein Rückruf in
|
||
`AppHost.Callbacks`; mit E-M2 beantwortet ihn das **eigene Fenster der Instanz**.
|
||
Über den Manager läuft davon nichts. (Eine frühere Fassung dieses Papiers hat den
|
||
Rückruf als Beleg für Kopflosigkeit angeführt — das war missverständlich: Der
|
||
Rückruf zeigt nur, dass die Anwendungsschicht ihre Umgebung nicht festschreibt.)
|
||
|
||
---
|
||
|
||
## 4 — Was der Manager protokolliert und meldet
|
||
|
||
**Nur, was aus seiner Sicht relevant ist:**
|
||
|
||
| Ereignis | Protokoll | Ans Deploymentcenter |
|
||
|---|---|---|
|
||
| Instanz gestartet | ja | Zustandswechsel |
|
||
| Instanz geordnet beendet | ja | Zustandswechsel |
|
||
| **Instanz abgestürzt** | ja | **Fehlermeldung stellvertretend** |
|
||
| Instanz startet wiederholt neu | ja | Fehlermeldung |
|
||
| Instanz antwortet nicht mehr auf dem Socket | ja | Fehlermeldung |
|
||
| Lizenz ungültig, Update fehlgeschlagen | ja | Fehlermeldung |
|
||
|
||
**Was die Instanz betrifft, bleibt in der Instanz.** Ein fehlgeschlagener
|
||
Werkzeugaufruf, ein abgebrochener Agentenlauf, eine überschrittene Budgetgrenze —
|
||
das protokolliert und meldet die Instanz selbst, mit ihrem eigenen Sub-Token.
|
||
Der Manager sieht davon nichts und soll davon nichts sehen.
|
||
|
||
Der Fall aus 1.1 ist genau der, für den es den Manager gibt: Die Instanz kann
|
||
nichts mehr sagen, also sagt er es an ihrer Stelle — mit Prozesskennung,
|
||
Beendigungsgrund und dem Ausschnitt aus ihrem Protokoll, der noch auf der Platte
|
||
liegt.
|
||
|
||
### 4.1 Jede Instanz meldet weiterhin selbst
|
||
|
||
Zwei Wege, mit Absicht:
|
||
|
||
- Die **Instanz** meldet, solange sie lebt — eigener Fehler-Stream, eigener
|
||
Heartbeat, eigener Watchdog-Monitor. Das Deploymentcenter führt Instanzen
|
||
bereits über `source` + `instance` als eigene Monitore.
|
||
- Der **Manager** meldet, was die Instanz nicht mehr melden kann.
|
||
|
||
Jeder Weg deckt den Ausfall des anderen ab. Ein Manager, der jede Meldung
|
||
durchreicht, wäre der neue Einzelpunkt.
|
||
|
||
---
|
||
|
||
## 5 — Lizenz und Token
|
||
|
||
**Der Manager hält das Master-Token, die Instanzen halten Sub-Token.**
|
||
|
||
Das Verfahren ist gebaut: `TokenProvisioner` tauscht über
|
||
`POST /api/tokens/v1/provision` das Master-Token gegen ein Sub-Token je Instanz,
|
||
mit den Rechten `watchdog:ping` und `bugtracker:report`. Rechte lassen sich dabei
|
||
nur einschränken, nie erweitern; jedes Sub-Token ist einzeln widerrufbar.
|
||
|
||
Zu tun bleibt der Umzug: Heute wird bei der Einrichtung provisioniert
|
||
(`setup.json`, `"source": "provision"`), künftig beim Anlegen einer Instanz durch
|
||
den Manager.
|
||
|
||
**Achtung bei der Projektbindung.** Seit SDK 2.8.1 erben Watchdog-Monitore das
|
||
Projekt ihres Ping-Tokens; ein Monitor mit `project_slug = NULL` erzeugt Befunde
|
||
im Scheinprojekt „default", und `dc_findings(project_slug=…)` liefert für eine
|
||
laufende, meldende Instanz dann **null Befunde**. Die Sub-Token müssen also
|
||
projektgebunden ausgestellt werden — sonst sieht die Aufsicht aus wie „nichts
|
||
auffällig".
|
||
|
||
Wir stehen auf **SDK 2.6.0**, aktuell ist **2.8.2**. **#218 ist damit
|
||
Voraussetzung, nicht Nacharbeit.**
|
||
|
||
---
|
||
|
||
## 6 — Einstellungen: die Aufteilung
|
||
|
||
### Der Manager (`manager.json`)
|
||
|
||
| Feld | Warum es nicht in die Instanz kann |
|
||
|---|---|
|
||
| `webUiPort`, `webUiBinding`, `webUiToken` | Eine Weboberfläche für alle |
|
||
| `licenseKey` | E-M3: die Lizenz gilt der Installation |
|
||
| `deploymentcenterUrl`, `deploymentcenterMasterToken` | Aus ihm entstehen die Sub-Token |
|
||
| `instancesDirectory` | Wo die Instanzen liegen — das muss er wissen, bevor es eine gibt |
|
||
| `updateChannel` | Aktualisiert wird das Programm, nicht die Instanz |
|
||
|
||
Mehr nicht. Farbschema, Teilerstellungen und Protokollstufe sind Werte einer
|
||
Oberfläche und gehören zu der, die sie anzeigt.
|
||
|
||
### Die Instanz (`InstanceSettings.json`)
|
||
|
||
Alles Übrige, und drei Felder ziehen aus den Anwendungseinstellungen dorthin
|
||
um — sie waren nie anwendungsweit, sie sahen nur so aus:
|
||
|
||
| Feld | Heute | Künftig |
|
||
|---|---|---|
|
||
| `logDirectory` | geteilt, alle Instanzen in denselben Dateien | `<Instanz>/Logs`, Vorbelegung |
|
||
| `backupDirectory` + `backupKeepCount` | geteilt, Präfixkollision bei ineinanderliegenden Namen | je Instanz |
|
||
| `autoBackupEnabled` + `autoBackupTime` | geteilt, ein Schalter für alle | je Instanz |
|
||
| `webServerPort` | je Instanz, Vorbelegung 7001, Kollision | **entfällt** — es gibt nur noch den Socket |
|
||
|
||
`autoBackupEnabled` und `autoBackupTime` standen in der ersten Fassung dieser Tabelle
|
||
nicht. Sie sind mitgegangen, weil die Hälfte eines Blocks zu verschieben eine
|
||
Einstellungsseite hinterlässt, auf der zwei von vier Sicherungsfeldern für alle Instanzen
|
||
gelten und zwei nur für eine — und weil ein Schalter, der die Sicherung für alle
|
||
einschaltet, derselbe Fehler ist wie ein Ordner für alle.
|
||
|
||
`deploymentcenterEnvironment` bleibt bei der Instanz: Eine Maschine kann eine
|
||
Produktions- und eine Testinstanz nebeneinander betreiben.
|
||
|
||
---
|
||
|
||
## 7 — Noch zu klären
|
||
|
||
**Sicherungen im Manager?** Dafür spricht viel: Die gemeinsame Rotation aus 1.2
|
||
verschwände, und eine Sicherung ließe sich auch von einer Instanz ziehen, die
|
||
gerade nicht läuft. Dagegen spricht eine Falle — **eine Sicherung
|
||
zurückzuspielen heißt, in ein Instanzverzeichnis zu schreiben, während die
|
||
Instanz womöglich läuft.** Das muss den Prozess erst anhalten, sonst schreibt man
|
||
unter einer offenen `state.db` das Dateisystem um. Wenn es kommt, dann mit dieser
|
||
Reihenfolge: anhalten, zurückspielen, starten — und nie ohne Rückfrage.
|
||
|
||
**Wie stellt eine Instanz fest, dass ein Manager da ist?** Vorschlag: Der Manager
|
||
startet sie und gibt ihr den Socketpfad mit; die Instanz verbindet sich einmal
|
||
zurück und beendet sich, wenn das nicht gelingt. Kein Selbstzweck-Handschlag,
|
||
sondern die Prüfung aus E-M3.
|
||
|
||
**Startet der Manager eine abgestürzte Instanz wieder?** Bisher nicht — er merkt es und
|
||
meldet es, mehr nicht. Dafür spricht, dass eine Instanz, die nachts um drei abstürzt, bis
|
||
zum Morgen tot bleibt. Dagegen spricht die Schleife: Eine Instanz, die an einer kaputten
|
||
Konfiguration scheitert, stürzt beim zehnten Versuch genauso ab wie beim ersten, und dann
|
||
hat man statt einer toten Instanz eine tote Instanz und ein volles Protokoll. Wenn es
|
||
kommt, dann mit Wartezeitverdopplung und einer Obergrenze, nach der er es sein lässt und
|
||
das meldet.
|
||
|
||
**Der Aufhänger aus 1.1 ist nicht geklärt** (#529). Er verschwindet mit dem
|
||
Umbau vermutlich von selbst, weil es den Selbst-Neustart nicht mehr gibt — aber
|
||
„vermutlich" ist kein Befund. Zwei unbelegte Verdachtsmomente: `BackupScheduler`
|
||
hat sechs `await` und kein einziges `ConfigureAwait(false)` und fängt sich damit
|
||
den Oberflächenfaden ein; `ShutdownRequested` ist ein `async void`-Behandler.
|
||
|
||
---
|
||
|
||
## 8 — Was aus #494 wird
|
||
|
||
Die Weboberfläche ist nicht umsonst gebaut. Sie zerfällt in zwei Teile, die
|
||
beide bleiben:
|
||
|
||
| Teil | Wohin |
|
||
|---|---|
|
||
| Endpunkte, DTOs, Maskierung, Ereignisstrom | **In die Instanz.** Statt auf einem TCP-Port hört Kestrel auf dem Socket. Der Code bleibt, die Bindung ändert sich |
|
||
| `wwwroot` (Gemini), Anmeldung, Token, Rahmen | **In den Manager.** Dazu die Instanzauswahl und die Verwaltungsansichten |
|
||
| Weiterleitung `/api/instanzen/{id}/…` → Instanz | **Neu im Manager** |
|
||
|
||
Der Ereignisstrom ist die eine Stelle, die wirklich neu gedacht werden muss: N
|
||
Instanzen, ein Strom zum Browser. Der Manager hält je laufender Instanz eine
|
||
Verbindung und führt sie zusammen, mit der Instanzkennung an jedem Ereignis.
|
||
|
||
---
|
||
|
||
## 9 — Reihenfolge
|
||
|
||
| Stufe | Inhalt | Woran man es misst |
|
||
|---|---|---|
|
||
| 1 ✔ | Einstellungen und Protokolle je Instanz trennen (Abschnitt 6) | Zwei Instanzen gleichzeitig, keine schreibt der anderen ins Protokoll |
|
||
| 2 ✔ | Instanzprozess kopflos lauffähig, Kestrel auf dem Socket | Eine Instanz läuft ohne Fenster, der Socket antwortet |
|
||
| 3 ◑ | Manager: anlegen, starten, anhalten, Prozessaufsicht, Lizenz, Sub-Token | Eine Instanz abschießen — der Manager merkt es und meldet es |
|
||
| 4 ◑ | Weboberfläche zieht um, Weiterleitung, ein Ereignisstrom | Eine Adresse, ein Zugang, Wechsel ohne Neustart |
|
||
| 5 ✔ | Instanzfenster als eigener Prozess, Wechsel holt das Fenster nach vorn | Kein Neustart mehr beim Wechseln |
|
||
| 6 | Sicherungen im Manager (falls Abschnitt 7 dafür entscheidet) | Zurückspielen hält die Instanz an und startet sie wieder |
|
||
|
||
**Stufe 1 ist die, die man überspringen möchte und nicht überspringen darf.**
|
||
Solange alle Instanzen in dieselben Protokolldateien schreiben, ist jede
|
||
Fehlersuche im Mehrinstanzbetrieb Ratearbeit — und genau in dieser Lage ist der
|
||
Vorfall aus 1.1 entstanden.
|
||
|
||
### 9.1 Was Stufe 1 gebracht hat
|
||
|
||
Fünf Schlüssel sind aus `AppSettings` in `InstanceSettings.json` gewandert:
|
||
`logDirectory`, `backupDirectory`, `backupKeepCount`, `autoBackupEnabled`,
|
||
`autoBackupTime`. Leer heißt jetzt `<Instanz>/Logs` und `<Instanz>/Backups`; ein
|
||
relativer Wert zählt weiterhin gegen das Instanzverzeichnis (#375).
|
||
|
||
**Keine Übernahme der Altwerte, aber eine Ansage.** Ein anwendungsweiter Pfad, in alle
|
||
Instanzen kopiert, wäre wieder derselbe Ordner für alle. Stehen die Schlüssel noch in
|
||
einer alten `Settings.json`, meldet `SettingsManager.AbandonedKeys` sie, und der Start
|
||
protokolliert einmal, was nicht mehr gilt und wohin diese Instanz stattdessen schreibt.
|
||
Beim nächsten Speichern verschwinden sie aus der Datei.
|
||
|
||
**`webServerPort` bleibt vorerst stehen.** Es entfällt erst mit **Stufe 4**, wenn der
|
||
Manager den Port übernimmt — nicht schon, sobald Kestrel den Socket hat. Eine frühere
|
||
Fassung dieses Absatzes sagte „mit Stufe 2"; das war falsch gerechnet: Solange es keinen
|
||
Manager gibt, der die Weboberfläche trägt, hieße Wegnehmen, sie ersatzlos abzuschalten.
|
||
|
||
---
|
||
|
||
## 10 — Was das für Vorhandenes bedeutet
|
||
|
||
**„Kern als Dienst, Oberfläche als Client"** aus dem Roadmap-Rückstau wird
|
||
überholt: Dort ging es um *einen* Kern und *eine* Oberfläche. Der dort notierte
|
||
Auslöser — „lohnt sich, sobald der erste UI-Absturz eine Nacht Agentenarbeit
|
||
kostet" — ist mit 1.1 eingetreten.
|
||
|
||
**#214 (24/7-Betriebsform)** sagt heute „Kein kopfloser Betrieb: Die Oberfläche
|
||
läuft laut Zielbetrieb immer mit". Das gilt nach E-M2 in anderer Form: Der
|
||
Manager läuft immer mit, die Instanzfenster laufen mit, aber eine Instanz *kann*
|
||
ohne Fenster laufen.
|
||
|
||
**#218 (SDK 2.6.0 → aktuell)** wird Voraussetzung — siehe Abschnitt 5.
|
||
|
||
### 9.2 Was Stufe 2 gebracht hat
|
||
|
||
**Kestrel hört auf dem Socket** — `<Instanz>/manager.sock` unter Linux, unter Windows
|
||
eine Named Pipe, deren Name aus einem Fingerabdruck des Instanzpfades entsteht
|
||
(`InstanceEndpoint`). Zwei Instanzen bekommen zwangsläufig zwei Adressen, ohne dass
|
||
jemand etwas vergeben müsste.
|
||
|
||
**Der Socket ist immer da, auch ohne Weboberfläche.** Er ist nicht Teil von #494, sondern
|
||
die Steuerfläche der Instanz: `webUiEnabled` schaltet den TCP-Zugang, nicht den Socket.
|
||
Was aus `WebUiHost` wurde, heißt deshalb jetzt `InstanceHttpHost` — ein Kestrel, zwei
|
||
Zugänge.
|
||
|
||
**Der Torwächter unterscheidet die Zugänge.** Über TCP gilt Token-Pflicht, über den
|
||
Socket nicht: Dort steht die Grenze am Dateisystem (0600 beziehungsweise
|
||
`CurrentUserOnly`), und wer die Socketdatei öffnen darf, sieht ohnehin in das Verzeichnis,
|
||
in dem alles liegt, was ein Token schützen würde. **Woran die beiden auseinandergehalten
|
||
werden, ist der Punkt, an dem ein Irrtum teuer wäre:** Bei Socket-Verbindungen stehen in
|
||
`HttpContext.Connection` keine IP-Adressen — daraus zu schließen „keine IP, also Socket"
|
||
wäre eine Vermutung an der Stelle, an der ein Torwächter steht, und ließe im Zweifel eine
|
||
TCP-Anfrage ohne Token durch. Der Socket-Zuhörer hängt deshalb jeder Verbindung selbst
|
||
eine Marke an (`LocalTransport`); wo sie fehlt, gilt Token-Pflicht.
|
||
|
||
**Ein belegter Port kostet nur noch die Weboberfläche.** Vorher gab `TryStart` in dem Fall
|
||
`null` zurück und die ganze HTTP-Fläche war weg. Das ist seit dem Socket die falsche
|
||
Rangfolge: Ihn zu verlieren, weil eine zweite Instanz schon läuft, hieße die Instanz
|
||
unerreichbar zu machen — in genau dem Fall, für den es den Manager gibt.
|
||
|
||
**Kopfloser Betrieb** über `--headless --instance <Pfad>`. Die Verzweigung sitzt in
|
||
`Program.Main` vor `BuildAvaloniaApp`, damit auf einem Server ohne Bildschirmsitzung kein
|
||
Fenstersystem angefasst wird. Gefragt wird nichts: Instanz aus dem Argument, Lizenz über
|
||
`NonInteractiveLicensePrompt`, Telegram-Anmeldung aus.
|
||
|
||
Nachgemessen, nicht angenommen: Eine kopflose Instanz auf `Instance-Test` lief ohne
|
||
Fenster, und eine rohe HTTP-Anfrage aus einem fremden Prozess (PowerShell, direkt auf die
|
||
Pipe) bekam `200 OK` auf `/api/info` — ohne Token.
|
||
|
||
**Zwei Lücken, die offen bleiben.** Die Socketdatei-Fälle (liegengebliebene Leiche,
|
||
Aufräumen beim Beenden) sind unter Windows gegenstandslos und werden dort mit Begründung
|
||
übersprungen — der Unix-Domain-Socket selbst ist auf der Entwicklungsmaschine also
|
||
ungeprüft, obwohl er die Zielplattform ist. Und das geordnete Ende auf `SIGTERM` ließ sich
|
||
hier nicht auslösen: Windows kennt das Signal für einen fensterlosen Prozess nicht.
|
||
Beides gehört auf die Linux-Testmaschine.
|
||
|
||
### 9.3 Was Stufe 3 bisher gebracht hat
|
||
|
||
**Die Aufsicht steht, Lizenz und Sub-Token fehlen noch.** Neu ist das Projekt
|
||
`ClawdDotNet.Manager` mit genau **einem** Projektverweis: `ClawdDotNet.Core`. Das ist
|
||
keine Sparsamkeit, sondern die halbe Architektur — `ClawdDotNet.App` schleppt sechzehn
|
||
Werkzeugprojekte hinter sich her, und „keine Werkzeuge im Manager" soll eine Eigenschaft
|
||
des Bauplans sein und keine Absichtserklärung. Damit `InstanceDirectoryManager` von beiden
|
||
Seiten erreichbar ist, ist er nach `Core/Instances` gezogen; dorthin gehört er ohnehin,
|
||
denn er beschreibt, wie eine Instanz auf der Platte liegt.
|
||
|
||
**Der Socket ist die Auskunftsquelle, nicht die Prozessliste.** Das ist die eine
|
||
Entscheidung, an der alles hängt. Die Prozessliste sagt, *dass* etwas da ist — sie sagt
|
||
nicht, ob es noch arbeitet, und genau das war der Zustand aus 1.1: Die Anwendung stand
|
||
einwandfrei in der Prozessliste, bis Windows sie als „reagiert nicht mehr" beendet hat.
|
||
Ein Aufsichtsdurchgang fragt deshalb den Socket und kennt drei Antworten:
|
||
|
||
| Socket | Prozess | Befund |
|
||
|---|---|---|
|
||
| antwortet | — | läuft |
|
||
| schweigt | ist weg | **Absturz**, mit Abschiedscode |
|
||
| schweigt | lebt | **verstummt** — nach genügend Fehlversuchen in Folge |
|
||
|
||
Nicht beim ersten Aussetzer: Eine einzelne verpasste Antwort ist Alltag, und ein Alarm bei
|
||
jedem Aussetzer ist nach einer Woche der Alarm, den niemand mehr liest.
|
||
|
||
**Ein Start gilt erst, wenn die Instanz antwortet.** Zwischen „der Prozess läuft" und „die
|
||
Instanz läuft" liegen Datenbank, Agenten und Kestrel. Wer nur auf `Process.Start` wartet,
|
||
hält eine Instanz für gestartet, die gleich an einer kaputten Konfiguration scheitert.
|
||
|
||
**Angehalten wird durch Bitten, nicht durch Schießen.** Neu auf der Instanzseite:
|
||
`POST /api/instanz/beenden`, **ausschließlich über den Socket** — über TCP gibt es den
|
||
Endpunkt nicht, auch mit gültigem Token nicht, und er antwortet dort mit 404 statt 403.
|
||
Die Lebensdauer einer Instanz gehört dem Manager; eine Browsersitzung soll den Prozess,
|
||
der sie bedient, nicht beenden können. Erst nach Ablauf der Frist wird hart beendet.
|
||
|
||
**Wiederfinden nach einem Managerausfall (E-M6).** Die Zustandsdatei
|
||
`<Instanz>/instance-state.json` ist der *Hinweis*, der Socket ist der *Beweis*: Erst wird
|
||
gefragt, wer da antwortet, und nur wenn dieselbe Instanzkennung zurückkommt, wird die
|
||
Prozesskennung aus der Datei überhaupt angefasst. Andersherum — Prozess suchen, finden,
|
||
für die eigene Instanz halten — führt nach einem Neustart der Maschine geradewegs dazu,
|
||
ein fremdes Programm zu beaufsichtigen und beim Anhalten zu beenden. Startzeit und
|
||
Prozesskennung zusammen entlarven eine wiederverwendete Nummer.
|
||
|
||
**Selbsttätiger Neustart ist bewusst nicht gebaut.** Die Abnahmebedingung lautet „merkt es
|
||
und meldet es", und zum Neustarten trifft dieses Papier keine Entscheidung. Eine Instanz
|
||
in einer Schleife wieder hochzufahren, die jedes Mal abstürzt, wäre die falsche Vorgabe
|
||
für etwas, das niemand entschieden hat. **Das ist eine offene Frage** — siehe Abschnitt 7.
|
||
|
||
**Das Register und die stellvertretende Meldung.** `InstanceRegistry` hält je Instanz eine
|
||
Aufsicht und hat zwei Zusagen, die beide aus E-M6 folgen: Beim Start wird *übernommen*,
|
||
nicht gestartet — ein Manager, der beim Hochkommen alles hochfährt, was er findet, startet
|
||
nach einem Neustart der Maschine auch das, was jemand mit Absicht angehalten hat. Und beim
|
||
Beenden wird nichts beendet: Wer den Manager schließt, will den Manager schließen. Die
|
||
einzige Ausnahme ist `StopAllAsync` — für einen Grund, der alle betrifft, und der heißt
|
||
Lizenzwiderruf.
|
||
|
||
`SupervisionReporter` macht aus einem Befund eine Meldung im Bugtracker, **stellvertretend**:
|
||
mit Prozesskennung, Abschiedscode und dem Ausschnitt aus dem Protokoll der Instanz, der
|
||
noch auf der Platte liegt. Der Idempotenzschlüssel ist stabil je Instanz und Art — stürzt
|
||
dieselbe Instanz wiederholt ab, zählt der Server hoch, statt den Bugtracker mit Zwillingen
|
||
zu füllen; wiederholte Abstürze sind ein Befund und nicht ein Dutzend gleicher.
|
||
|
||
**Ein leerer Protokollauszug ist die aussagekräftigste Zeile der Meldung.** Genau das war
|
||
der Zustand aus 1.1: Der Prozess wurde erschlagen, bevor sein Schreibpuffer geleert war,
|
||
und hinterließ nichts. Die Meldung sagt das ausdrücklich, statt eine leere Stelle zu
|
||
lassen.
|
||
|
||
**`manager.json`** trägt die acht Felder aus Abschnitt 6 und sonst nichts. Lizenzschlüssel,
|
||
Master-Token und Web-Token liegen darin verschlüsselt und zur Laufzeit im Klartext — wie
|
||
in den Anwendungseinstellungen, mit Absicht dieselbe Bauart. Unsinnige Aufsichtswerte
|
||
(Abfragetakt 0, Alarm nach null Fehlversuchen) werden eingefangen: Die Datei lässt sich von
|
||
Hand bearbeiten.
|
||
|
||
**Ein Defekt, den erst die Tests gezeigt haben.** Eine angehaltene Instanz erschien als
|
||
`Instance-Alpha` statt `Alpha` — die Aufsicht kennt den Namen erst, wenn die Instanz einmal
|
||
geantwortet hat, und eine angehaltene tut das nie. Genau diese Ansicht zeigt der Manager
|
||
aber an. Das Register führt den Namen jetzt aus `InstanceSettings.json` mit.
|
||
|
||
**Abnahme, mit echten Prozessen gemessen.** Der Manager hat die Instanz `Test` in 0,7 s
|
||
gestartet und über die Named Pipe identifiziert, die Zustandsdatei geschrieben
|
||
(`pid=54476`, `id=d68af6fd`), dann wurde der Prozess von außen abgeschossen. Der nächste
|
||
Durchgang meldete `Crashed`, Abschiedscode −1, meldenswert, und räumte die Zustandsdatei
|
||
weg. Dazu 20 Tests gegen gesteuerte Prozesse — ein Absturz zum gewünschten Zeitpunkt und
|
||
eine wiederverwendete Prozesskennung sind mit einem echten Programm nicht herzustellen.
|
||
|
||
**Ein Hinweis zur Messumgebung.** Die Abnahme lief über einen eigens gebauten Treiber
|
||
außerhalb der Werkzeugumgebung. Ein Prozess, den ein Agentenwerkzeug hier startet, landet
|
||
in einem Job-Objekt, das seine Kinder sofort mitnimmt; der Instanzprozess starb dadurch,
|
||
bevor er die erste Protokollzeile schreiben konnte. Das ist ein Artefakt der Umgebung und
|
||
keine Eigenschaft des Managers — aber es heißt, dass ein Abnahmetest dieser Art nicht in
|
||
die gewöhnliche Testreihe gehört.
|
||
|
||
### 9.4 Lizenz und Sub-Token im Manager
|
||
|
||
**Ein neues Projekt: `ClawdDotNet.Licensing`.** `LicenseGate`, `LicenseWatch`,
|
||
`LicenseInfo` und `ILicensePrompt` sind aus `ClawdDotNet.App` dorthin gezogen. Der Grund
|
||
ist der Schnitt: Zwei Prozesse brauchen dieselbe Lizenzprüfung, und keiner von beiden darf
|
||
den anderen kennen. In `Core` konnte es nicht landen — dann zöge jedes der sechzehn
|
||
Werkzeugprojekte das Deploymentcenter-SDK mit.
|
||
|
||
Dabei ist die Kopplung an `SettingsManager` verschwunden: `LicenseGate` kennt jetzt nur
|
||
`ILicenseStore` — Serveradresse lesen, Schlüssel lesen und schreiben, speichern. Die
|
||
Anwendung führt ihn in `Settings.json`, der Manager in `manager.json`. Der Torwächter prüft
|
||
Schlüssel; Dateien verwaltet er nicht. Die Anwendungsversion kommt als Parameter statt aus
|
||
einem globalen Zugriff — diese Schicht weiß nicht, welche Anwendung sie benutzt.
|
||
|
||
**`ManagerCore` ist die Klammer:** eigene Einstellungen, Lizenz, Register, Meldeweg. Was
|
||
darüber liegt — ein Fenster, die eine Weboberfläche für alle — setzt darauf auf.
|
||
|
||
- **Ohne gültige Lizenz startet er keine Instanz.** Laufende werden trotzdem in die Liste
|
||
genommen: Man soll sie sehen — und anhalten können.
|
||
- **Ein Widerruf hält alle an.** Der einzige Fall, in dem der Manager das von sich aus
|
||
tut. Ein Serverausfall gehört ausdrücklich nicht dazu; das SDK trennt Urteil und
|
||
gescheiterten Versuch, und nur das Urteil kommt hier an. Sonst nähme ein Netzproblem
|
||
sämtliche Installationen gleichzeitig mit.
|
||
- **Ohne hinterlegte Lizenzprüfung sperrt er nichts.** Sonst gäbe es eine Henne-Ei-Sperre,
|
||
bevor überhaupt jemand einen Schlüssel eintragen kann.
|
||
|
||
**Sub-Token je Instanz.** Der Manager hält das Master-Token und tauscht es beim **Anlegen**
|
||
einer Instanz gegen ein eigenes — `watchdog:ping` und `bugtracker:report`, einzeln
|
||
widerrufbar. Beim Anlegen und nicht beim ersten Start, weil die Instanzdatei dann
|
||
zweifelsfrei niemandem gehört; sie später zu schreiben, während der Instanzprozess läuft,
|
||
hieße zwei Schreiber auf einer Datei. Für Instanzen, die es vor dem Manager schon gab, wird
|
||
es vor dem Start nachgeholt.
|
||
|
||
Danach wird nachgefragt (`whoami`), ob das neue Token an ein Projekt gebunden ist — seit
|
||
SDK 2.8.1 erbt ein Watchdog-Monitor das Projekt seines Ping-Tokens, und ohne Bindung landen
|
||
die Befunde im Scheinprojekt „default". Ein fehlgeschlagener Tokenbezug hält die Instanz
|
||
nicht auf: Monitoring, das nur bei perfekter Rechtelage läuft, ist genau dann still, wenn
|
||
man es braucht.
|
||
|
||
**Was noch fehlt, damit E-M3 vollständig ist:** Die Instanzen prüfen die Lizenz weiterhin
|
||
selbst. Das lässt sich erst abschalten, wenn eine Instanz feststellen kann, ob ein Manager
|
||
da ist (Abschnitt 7) — vorher entstünde eine Fassung ganz ohne Durchsetzung. Bis dahin
|
||
führen beide ihren eigenen Schlüssel.
|
||
|
||
**Ein Testfehler, der zweimal derselbe war.** In den Manager-Tests hatte ich die Antwort des
|
||
Sockets hinterlegt, *bevor* der Manager hochfuhr — damit sagt man „diese Instanz läuft
|
||
bereits", der Manager übernimmt sie (E-M6), der Starter wird nie angefasst, und der Test
|
||
prüft etwas anderes, als er behauptet. Es gibt jetzt eine Hilfsmethode, die genau das
|
||
ausschließt.
|
||
|
||
### 9.5 Was Stufe 4 gebracht hat
|
||
|
||
**Den Managerprozess.** `ClawdDotNet.Instanzmanager` ist eine Konsolenanwendung: Sie prüft
|
||
die Lizenz, übernimmt, was läuft, hängt die Weboberfläche auf und wartet. Sie beendet beim
|
||
Herunterfahren keine Instanz.
|
||
|
||
**Zwei neue Projekte.** `ClawdDotNet.Manager.Web` trägt die Weboberfläche — ohne jeden
|
||
Verweis auf `ClawdDotNet.Web`, denn dort hängt `App` und daran sechzehn Werkzeugprojekte.
|
||
`ClawdDotNet.ManagerApp` ist der Einstiegspunkt. `wwwroot` ist aus der Instanz in den
|
||
Manager gezogen (Abschnitt 8).
|
||
|
||
**Der Schnitt der HTTP-Fläche.** Unter `/api/verwaltung` steht, was den Manager angeht:
|
||
Instanzen auflisten, anlegen, starten, anhalten. **Alles andere unter `/api` geht
|
||
unverändert an eine Instanz** — er leitet weiter, er implementiert nicht. Welche Instanz
|
||
gemeint ist, sagt die Kopfzeile `X-Instanz`; läuft genau eine, ist sie gemeint, und laufen
|
||
mehrere, wird nachgefragt statt geraten. Eine Anfrage an die falsche Instanz ist schlimmer
|
||
als eine abgelehnte.
|
||
|
||
**Der zusammengeführte Ereignisstrom.** Der Manager hält je laufender Instanz eine
|
||
Zuleitung und schreibt an jedes Ereignis, von wem es kommt (`instanz`, `instanzName`).
|
||
Ergänzt statt verpackt: Ein Umschlag wäre einfacher gewesen, hätte aber jede Ansicht der
|
||
Weboberfläche zum Umschreiben gezwungen.
|
||
|
||
**Abnahme, gegen laufende Prozesse gemessen:**
|
||
|
||
| Geprüft | Ergebnis |
|
||
|---|---|
|
||
| `/api/verwaltung/instanzen` ohne Token | 401 |
|
||
| Oberfläche ohne Token | 200 — die leere Hülle, wie vorgesehen |
|
||
| Instanz über den Manager starten | `Running`, PID 18104 |
|
||
| `/api/info` durchgereicht | Antwort **der Instanz** (`instanceId d68af6fd`) |
|
||
| `/api/status` durchgereicht | 503 der Instanz, unverfälscht |
|
||
| `/events/logs` | Zeilen der Instanz, mit `"instanz":"Instance-Test"` daran |
|
||
| Anhalten über den Manager | `Stopped` |
|
||
| **Manager abgeschossen** | Instanz läuft weiter (E-M6) |
|
||
| **Manager neu gestartet** | „1 Instanzen bekannt, 1 laufen bereits" — übernommen |
|
||
|
||
**Drei Defekte, die erst der Lauf gezeigt hat:**
|
||
|
||
1. **Der Manager hing an einer Konsolenabfrage.** `ConsoleLicensePrompt` wartete auf eine
|
||
Eingabe, die niemand geben konnte; die Weboberfläche kam nie hoch, und im Protokoll
|
||
stand nichts davon, weil die Frage auf der Standardausgabe steht. Ein Test auf
|
||
`Console.IsInputRedirected` reicht dafür **nicht**: Er beantwortet „ist die Eingabe
|
||
umgeleitet", nicht „kann jemand antworten" — ein Prozess ganz ohne Konsole, der
|
||
gefährlichste Fall, ist nicht umgeleitet. Der Manager fragt jetzt gar nicht; ein
|
||
Schlüssel gehört in `manager.json`.
|
||
2. **Eine unlesbare `manager.json` wurde stillschweigend durch Vorgaben ersetzt.** Der
|
||
Manager suchte dann im falschen Verzeichnis, hörte auf einem anderen Port und fragte
|
||
nach einer Lizenz, die woanders stand — und nichts sagte, woran es lag. Es kostete eine
|
||
halbe Stunde und einen fehlenden Backslash. `LoadProblem` meldet den Grund jetzt.
|
||
3. **Der Instanzprozess erbte `CLAWD_CONFIG_DIR` vom Manager** und legte sich im
|
||
Konfigurationsverzeichnis des Managers eine eigene `Settings.json` an. Eine
|
||
Umgebungsvariable vererbt sich, ein Argument nicht — deshalb nimmt der Manager sein
|
||
Konfigurationsverzeichnis jetzt über `--config`. Für eine systemd-Unit ist das ohnehin
|
||
der geradere Weg.
|
||
|
||
**Was in Stufe 4 noch fehlt:** Die Weboberfläche selbst kennt noch keine Instanzen — sie
|
||
spricht `/api/…` an und bekommt die eine laufende. Instanzauswahl, `X-Instanz` und die
|
||
Verwaltungsansicht sind Arbeit an `wwwroot` und gehören damit in Geminis Spur. Und
|
||
`webServerPort` steht noch in der Instanz: Ihr TCP-Zugang ist erst überflüssig, wenn die
|
||
Oberfläche im Manager vollständig ist.
|
||
|
||
### 9.6 Was Stufe 5 gebracht hat
|
||
|
||
**Der Wechsel ist kein Neustart mehr.** Vorher hieß „Instanz wechseln": Der laufende
|
||
Prozess fährt sich herunter und startet sich mit einer anderen Instanz neu — wer
|
||
wechselte, beendete zwangsläufig die Instanz, aus der heraus er es tat, und genau dabei
|
||
hat sich die Anwendung am 2026-09-08 aufgehängt. Jetzt laufen alle, und es kommt eine nach
|
||
vorn.
|
||
|
||
**Die Instanz holt ihr Fenster selbst.** Neu ist `POST /api/instanz/fenster`, wieder
|
||
ausschließlich über den Socket. Der Manager kennt kein Fenstersystem und soll keines
|
||
kennen; er bittet, und wer ein Fenster hat, holt es nach vorn. Kopflos läuft die Bitte ins
|
||
Leere, und das ist die richtige Antwort. **Das letzte Wort hat der Fenstermanager** —
|
||
Windows lässt einen Hintergrundprozess den Vordergrund nicht einfach an sich reißen; der
|
||
kurze Ausflug über `Topmost` half im Versuch, unter Linux entscheidet der Fenstermanager.
|
||
Mehr als bitten kann eine Anwendung nicht.
|
||
|
||
**`--managed` schaltet den Wechsel im Instanzfenster ab.** Der Manager gibt es jedem
|
||
Instanzprozess mit; das Fenster gräut seinen Instanz-Schalter aus und erklärt auf Nachfrage,
|
||
wo gewechselt wird. Zwei Schlösser an derselben Tür: Der Knopf ist aus, und der Weg
|
||
dahinter prüft es noch einmal — hinter ihm wartet ein Selbst-Neustart.
|
||
|
||
**`instanceWindows` in `manager.json`**, vorbelegt auf `true`: Jede Instanz behält ihr
|
||
Fenster (E-M2). Auf einer Maschine ohne Bildschirmsitzung gehört `false` hin.
|
||
|
||
**Abnahme, an echten Fenstern gemessen:**
|
||
|
||
| Geprüft | Ergebnis |
|
||
|---|---|
|
||
| Instanz über den Manager gestartet | PID 18128, Fenstertitel „ClawdDotNet" |
|
||
| Befehlszeile | `--managed --instance …` |
|
||
| Fenster von außen minimiert | `IsIconic = True` |
|
||
| `POST /instanzen/Instance-Test/fenster` | „Fenster von 'Test' nach vorn geholt" |
|
||
| Danach | `IsIconic = False`, **im Vordergrund** |
|
||
| Prozess danach | **PID 18128, unverändert** — nichts neu gestartet |
|
||
|
||
Die letzte Zeile ist die Abnahmebedingung: kein Neustart mehr beim Wechseln.
|