Commit Graph
54 Commits
Author SHA1 Message Date
RichardandClaude Opus 5.5 f627918729 Lagebild, Stufe 0: Teamuebersicht in der WebUI (#711)
Wer arbeitet, wer hat in den letzten Sekunden wen erreicht, und wo klemmt es,
auf einen Blick. Stufe 0 kommt ohne Einheiten aus; mit #683 werden die Agenten
zu aufklappbaren Containern, und die Ansicht faellt mit #688 zusammen.

Kern:
- AgentActivity: fuehrt die laufenden Laeufe (Beginn, Quelle, Aufgabe,
  Aufrufer) und die letzten Verbindungen (begrenzt auf 500), meldet Start,
  Ende und Verbindung als Ereignis. Vorher war nur ein Lauf ueber ChatAsync
  "laufend"; ein frischer Lauf (@new) war unsichtbar, und niemand hielt fest,
  wer wen gerufen hatte.
- Die Engine meldet beide Einstiege an; "arbeitet" beginnt hinter dem
  Agententor.

Anwendung:
- TeamOverview stellt den Lagebericht zusammen. Die Zustaende entscheidet der
  Code aus erfassten Tatsachen: laufender Lauf, offene Freigabe, Status des
  letzten Belegs, Laufzeit gegen den Median des Agenten (haengt ab dem
  Dreifachen, fruehestens nach 5 Minuten, ohne Verlauf nach 15). Rangfolge:
  haengt, wartet auf Freigabe, arbeitet; Problem und Budget nur, solange
  nichts Neues laeuft.

Web:
- GET /api/team, Ereignis "activity" (started, finished, link); das Ereignis
  "run" traegt jetzt die Agenten-Id.
- Ansicht #/team mit Cytoscape.js und ELK (bei Bedarf geladen): Layout aus
  der Struktur statt von Hand, Farbe und Symbol je Zustand, Kanten verblassen
  nach 30 s, Klick oeffnet eine Seitenleiste (laufende Laeufe, Freigaben,
  letzter Lauf im Wortlaut) mit "Freigaben oeffnen" vorne, danach Chat und
  Laeufe. Sprachpakete en/de.
- Ein Layout, das lief, solange die Flaeche noch 0 px breit war, wird neu
  gerechnet, sobald sie eine Breite hat (im Pruefgestell gefunden).

Geprueft im Browser mit einem Pruefgestell (API und Ereignisstrom durch
Attrappen ersetzt): sieben Agenten in allen Zustaenden, Seitenleiste,
Live-Kante und Verblassen.

Tests: AgentActivityTests (8), TeamOverviewTests (14). 1.283 gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 12:34:46 +02:00
RichardandClaude Opus 5.5 0f49e04261 Test: wackelnde Wanduhr-Pruefung aus dem Parallelitaetstest entfernt (#551)
Freigegebene_Werkzeuge_laufen_tatsaechlich_gleichzeitig pruefte neben dem
Hoechststand gleichzeitiger Aufrufe auch eine Laufzeit unter 750 ms. Am
2026-09-24 schlug sie nach dem Merge von #716 mit 927 ms fehl, als die
Testprojekte parallel liefen; beim erneuten Lauf war sie gruen. Die Zeit misst
die Last der Maschine, nicht die Engine. Der Hoechststand bleibt als Nachweis.
Vier volle Durchlaeufe danach ohne Fehler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 12:16:56 +02:00
RichardandClaude Opus 5.5 32383119da Budgetende, Stufe 4: den Engpass melden, bevor er eintritt (#716)
- TaskSchedule.OccurrenceTimesBetween liefert die Zeitpunkte der restlichen
  Laeufe; OccurrencesBetween zaehlt sie nur noch.
- BudgetHeadroom implementiert IBudgetOutlook: Die restlichen geplanten Laeufe
  des Tages werden in zeitlicher Reihenfolge durchgegangen, je Agent mit
  Grenze und fuer die Instanz, bis zum ersten Lauf, der das Budget verbraucht
  vorfaende. Das spiegelt die Pruefung beim Start eines Laufs. Spaetwerk
  zaehlt nicht mit, es laeuft ohnehin nur im Spielraum.
- Der Scanner schaut alle 30 Minuten voraus und nennt je Agent und Tag einmal,
  wann das Budget ausgeht und wie viele der geplanten Laeufe dann ausfallen,
  solange noch Zeit ist, nachzulegen oder zu entscheiden, was wartet.

Die Meldung steht vorerst im Protokoll (Warnung). Der Weg zum Menschen ueber
einen Kanal gehoert zu #646, die Anzeige ins Lagebild (#711).

Tests: 4 neu fuer die Vorschau. 1.261 gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 12:12:44 +02:00
RichardandClaude Opus 5.5 9cf9a2cfc2 Budgetende, Stufe 3: Nachholen nur im Spielraum des Tages, keine Kappung bei 500 (#716)
- TaskSchedule.OccurrencesBetween zaehlt, wie oft eine Aufgabe bis zu einem
  Zeitpunkt noch laeuft, einschliesslich eines jetzt faelligen Laufs.
- BudgetHeadroom (ILateRunGate): Ein mitgenommener oder nachzuholender Lauf
  startet nur, wenn er in den Spielraum passt: Grenze minus Verbrauch minus
  Reservierung minus Bedarf der restlichen geplanten Laeufe bis Mitternacht
  minus 10 % Reserve. Das gilt je Agent und je Instanz, fuer Kosten und Tokens.
  Anderes Spaetwerk zaehlt nicht als Bedarf, sonst blockierten sich
  mitgenommene Aufgaben gegenseitig.
- Der Scanner fragt den Spielraum nur fuer Spaetwerk und protokolliert, wenn es
  warten muss. Die Engine gibt ihren BudgetGuard heraus, damit Scanner und
  Engine dieselben Reservierungen sehen.
- Ersetzt den pauschalen 25-%-Anteil aus dem Konzept (dort nachgezogen): Ein
  fester Anteil ist an vollen Tagen zu viel, an ruhigen zu wenig.

Drei weitere stille Kappungen bei 500 beseitigt (ListAllAsync, ungekappt):
Kostenprognose (Aufgaben ueber 500 fehlten), Waisenabgleich beim Import und
task_find aus #674, wo Dubletten unter aelteren Aufgaben mit niedriger
Prioritaet unsichtbar wurden.

Tests: BudgetHeadroomTests (9), Scanner-Pforte (1), Dublette hinter 500
Aufgaben (1). 1.257 gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 12:10:40 +02:00
RichardandClaude Opus 5.5 8a83a67fb4 Budgetende, Stufe 2: laufende Laeufe reservieren ihr Budget (#716)
Der offene Rest aus #682: Jeder Lauf pruefte das Budget einzeln gegen den
bisher erfassten Verbrauch. Laufende Laeufe waren unsichtbar, gleichzeitig
startende passierten die Pruefung gemeinsam und ueberzogen gemeinsam.

- BudgetGuard.ReserveAsync prueft und reserviert unter einer Sperre. Die
  Reservierung ist der Mittelwert der letzten 20 Laeufe des Agenten (Laeufe
  ohne Verbrauch zaehlen nicht mit) und gilt, bis der Verbrauch des Laufs
  erfasst ist.
- Gegen die Grenze zaehlt: verbraucht + in laufenden Laeufen reserviert. Die
  eigene Schaetzung eines Laufs zaehlt bewusst nicht mit; ein einzelner Lauf
  wird weiter nur abgelehnt, wenn das Budget schon verbraucht ist. Sonst fiele
  eine kleine Aufgabe wegen eines grossen Durchschnitts aus.
- Gilt fuer Kosten- und Tokengrenze, je Agent und je Instanz.
- Die Ablehnungsmeldung nennt den reservierten Anteil, sonst wirkte "0,60 von
  1,00 USD verbraucht" als Ablehnungsgrund falsch.
- IUsageRepository.GetRecentForAgentAsync, mit leerer Vorgabe im Interface,
  damit bestehende Attrappen unberuehrt bleiben.

Tests: BudgetReservationTests (8 neu), darunter zehn gleichzeitige Starts, von
denen genau vier passieren. 1.246 gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 12:06:07 +02:00
RichardandClaude Opus 5.5 5f3a85595b Budgetende, Stufe 1: Ausfall vermerken, Regel je Agent, mitnehmen statt anrennen (#716)
Bisher teilte sich eine Budgetablehnung den Ausgang NichtGelaufen mit dem
Herunterfahren. Einmalige Aufgaben wurden den Rest des Tages alle 30 Minuten
erneut abgelehnt (jedes Mal ein Beleg) und liefen nach Mitternacht still zu
Lasten des neuen Tages. Wiederkehrende verfielen still.

- TaskLauf.KeinBudget als eigener Ausgang; der Dispatcher bildet
  BudgetExceeded darauf ab.
- Vier Felder an der Aufgabe: BudgetMissCount, LastBudgetMissAt,
  DeferredUntil, CatchUpPending (Spalten werden fuer bestehende Datenbanken
  nachgeruestet).
- Einmalige Aufgaben werden mitgenommen: Der Termin bleibt erhalten, faellig
  ist die Aufgabe wieder ab der naechsten lokalen Mitternacht, nicht vorher.
- Wiederkehrende Aufgaben folgen der Regel des Agenten
  (budget.whenExhausted): expire vermerkt den Ausfall und laesst den Termin
  verfallen; catchUpOnce fasst alle verpassten Termine zu einem Nachholauf am
  naechsten Tag zusammen. Ein regulaer faelliger Termin deckt das Nachholen
  mit ab, es bleibt bei einem Lauf.
- Der neue Tag hat Vorfahrt: Mitgenommenes und Nachholaeufe kommen nach allem
  regulaer Faelligen, unabhaengig von der Prioritaet.
- Beim ersten Takt eines neuen Tages eine Zusammenfassung je Agent im Log.
- Sichtbar: Die Aufgabenkarte der WebUI zeigt Ausfaelle, Mitnahme und
  Nachholen; die Regel ist im Desktop je Agent einstellbar. Nebenbei sind
  "Next:"/"Last:" auf der Karte ins Sprachpaket gewandert.

Stufen 2 bis 4 (Reservierung, Spielraum statt pauschalem Anteil, Vorbeugen)
folgen.

Tests: BudgetMissTests (9 neu). 1.238 gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 12:03:15 +02:00
RichardandClaude Opus 5.5 8df5d004e4 Scanner: Laeufe deckeln, stille Kappung bei 500 beseitigen (#682)
Drei Fehler im Taktgeber des Taskboards, zwei gemeldet, einer beim Bau gefunden:

1. Unbegrenzte Parallelitaet. Jede faellige Aufgabe startete sofort. Bei
   Cron-Terminen auf runden Zeiten hiess das: alle Agenten gleichzeitig, und
   alle Laeufe passierten die Budgetpruefung gemeinsam, weil sie je Lauf vor
   dem Start laeuft. Neu: maxParallelTaskRuns je Instanz (Vorgabe 4). Der Rest
   wartet, sortiert nach Prioritaet und dann nach Faelligkeit.

2. Stille Kappung. Die Kandidaten kamen ueber ListAsync mit Limit 500; darueber
   fielen Termine lautlos aus dem Takt. Neu: ListRunnableAsync liefert alle
   Aufgaben auf todo, ungekappt. Muessen Aufgaben warten, steht das im Log,
   allerdings nur, wenn sich die Zahl aendert.

3. Der Takt wartete auf jeden Lauf, den er startete. Ein zehnminuetiger Lauf
   hielt damit jeden anderen Termin zehn Minuten an, und mit einem Deckel
   haetten die freien Plaetze leer gestanden. Neu: Die Schleife startet und
   wartet nicht; DisposeAsync wartet stattdessen auf die laufenden Laeufe.

Beansprucht wird nur, was sofort starten kann. Alles andere bleibt auf todo,
sonst liefe beim Warten der Lease ab und die Aufgabe wuerde ein zweites Mal
beansprucht. Ein Cron-Termin geht dadurch nicht verloren, weil DueOccurrence
bis zum letzten Lauf zuruecksucht. Ein von Hand gestarteter Lauf wartet auf
keinen Platz, belegt aber einen.

Bleibt offen: Bis zu maxParallelTaskRuns Laeufe koennen die Budgetpruefung
weiterhin gemeinsam passieren. Die Luecke ist damit begrenzt, aber nicht
geschlossen; dafuer braeuchte es eine Reservierung vor dem Lauf.

Tests: ScannerCapacityTests (9 neu). 1.229 gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 10:24:51 +02:00
RichardandClaude Opus 5.5 ad71682c2d i18n: Englisch als Hauptsprache, Sprachpakete fuer Desktop und WebUI (#661)
Umbau durch Gemini Flash, Durchsicht und Nacharbeit durch Claude.

Umfang:
- Desktop: LocalizationManager (JSON-Pakete, Rueckfall auf Englisch,
  Umschalten zur Laufzeit), Localize-Markup-Erweiterung, Sprachwahl in den
  Einstellungen (AppSettings.Language, Vorgabe "en"); 317 Schluessel je Sprache
- WebUI: alle Ansichten auf t()/data-i18n, 656 Schluessel je Sprache
- Agenten-Werkzeuge: Beschreibungen, Schemata und Meldungen auf Englisch
- Tests an die englischen Meldungen angepasst, keine Pruefung abgeschwaecht

Nacharbeit bei der Durchsicht:
- ~25 Kommentarbloecke mit Begruendungen waren ersatzlos entfallen, darunter
  sicherheitsrelevante (NTFS-Datenstrom beim shared:-Praefix, Bremse gegen
  Agentenschleifen, doppelter POST beim Anlegen einer Instanz). Wieder
  eingesetzt: auf Englisch, wo die Datei umgestellt ist, sonst im Wortlaut.
- Drei WebUI-Schluessel fehlten (logs.paused, instances.instanceOfflineError,
  instances.noInstanceActiveError). Weil t() bei fehlendem Schluessel den
  Schluessel selbst liefert, griff der "|| err.message"-Rueckfall nie — die
  Oberflaeche haette den Schluesselnamen angezeigt.

Geprueft: Schluesselgleichheit en/de, Platzhalter je Schluessel, jeder im
Code benutzte Schluessel vorhanden, node --check fuer alle Module,
1.220 Tests gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 09:43:51 +02:00
RichardandClaude Opus 5 060dc6a6ad FileRW list sortiert und gedeckelt (#673), Taskboard task_find (#674)
Die Betriebskontrolle im Testbetrieb meldete Stundendateien als fehlend, die
es gab: 105 Eintraege in der Reihenfolge des Dateisystems, vierzeilig als
eingeruecktes JSON. list liefert jetzt die 50 zuletzt geaenderten Eintraege,
neueste zuerst, eine Zeile je Eintrag, mit Gesamtzahl im Kopf; pattern, sort,
order und limit steuern das.

Dabei aufgefallen (#675): Directory.GetFiles wertet Trennzeichen im Muster
aus - pattern "../*" fuehrte an der Pfadpruefung vorbei aus dem Verzeichnis
heraus, in list wie in read_many. Muster mit '/', '\' oder '..' werden jetzt
abgewiesen.

Und derselbe Befund ist keine zweite Aufgabe (#674): task_find beantwortet
die Frage vor dem Anlegen, task_create legt bei gleichbedeutendem Titel einer
offenen Aufgabe keine zweite an, sondern vermerkt das erneute Auftreten an
der bestehenden. force=true geht trotzdem, Erledigtes blockiert nichts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 21:19:19 +02:00
RichardandClaude Opus 5 24e457b11f FileRW: Sammel-Lesen (#663) und Hinweis auf den anderen Workspace (#662)
#663: Neue Aktion read_many liest mehrere Dateien in einem Aufruf - entweder
paths (Liste) oder path (Verzeichnis) mit pattern. Gedeckelt auf 50 Dateien und
200.000 Zeichen; was nicht mehr hineinpasst, wird namentlich genannt, damit der
Agent gezielt nachholen kann. Anlass: Der Tagesabschluss im Testbetrieb las 24
Stundendateien einzeln und starb zweimal an der Schrittgrenze - die soll
Endlosschleifen abfangen, nicht ehrliche Arbeit.

#662: Schlaegt read oder list fehl, weil der Pfad im anderen Workspace liegt,
sagt die Fehlermeldung das - aber nur, wenn der Agent diesen Workspace auch
benutzen darf. Guenstige Modelle vergassen workspace "shared" regelmaessig und
verbrauchten mit jedem Rateversuch einen Schritt.

Dateipruefung, Endungen und Workspace-Grenze gelten in read_many unveraendert;
eine verbotene Endung wird uebergangen statt den Lauf zu kippen.

Version 0.1.14.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 15:53:08 +02:00
RichardandClaude Opus 5 996330a77e Prompt history: list preview shows the message as written, not the engine's time line (#660)
Version 0.1.13.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 15:36:55 +02:00
RichardandClaude Opus 5 5adf4c1ca6 Prompt History und Verbrauchsauswertung (#660), englische Hauptsprache (#661)
Prompt history: every run records its transcript (task, each model step with
tokens and cost, tool calls and results, engine follow-ups) - bounded in size,
scrubbed of recognisable secrets, system prompts stored once per text.
Retention in days per instance (default 14) and per agent (0 = never, -1 =
forever); an hourly janitor deletes what is past it, "never" also purges.

Usage analysis over run receipts: filter by period, agent, model, event, status,
cost and free text; sort by every column; totals over the whole filter; hourly
or daily series grouped by agent, model or event; markers for budget denials and
model changes; run detail with receipt, transcript, tool calls and approvals.

WebUI: Analysis > Token usage > Runs / Timeline, run detail, Prompt History with
retention settings. New code, comments and texts in English; texts come from
language packs (i18n/en.json, de.json) via modules/i18n.js. CLAUDE.md records
English as the main language.

Version 0.1.12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 15:33:09 +02:00
RichardandClaude Opus 5 2f0361278b Freigaben buendeln statt Weckflut (#643), Freigabemodell ueber Werkzeugrechte (#641)
#643: Entscheidungen wecken den Agenten nur noch bei Ablehnung oder gescheiterter
Ausfuehrung, gesammelt in einer Aufgabe je Agent (5 min Ruhefenster), frisch per
@new:agent statt im fortgefuehrten Chatverlauf. Eine einmalige Aufgabe, deren Lauf
wegen Budget oder Herunterfahren gar nicht stattfand, bleibt faellig (Wiedervorlage
nach 30 min) statt mit gesetztem Marker liegenzubleiben.

#641: Rechte stehen in der Werkzeugkonfiguration des Agenten (erlaubt/verboten).
Erlaubtes laeuft, Nicht-Erlaubtes wird einzeln vorgelegt statt verweigert, Verbotenes
gar nicht erst. "Gleichartiges kuenftig erlauben" erteilt eine widerrufbare
Dauerfreigabe je Agent/Werkzeug/Operation; nach drei gleichartigen Freigaben schlaegt
die Oberflaeche sie vor. Aenderungen an Agenten bleiben ausnahmslos freigabepflichtig.
Database: Operation ist das SQL-Schluesselwort; die eigene Zugriffspruefung greift nur
noch ohne Freigabestelle (AgentToolContext.FreigabeGeprueft). WebUI: Rechte-Tabelle je
Werkzeug, Dauerfreigaben mit Widerruf.

Version 0.1.11.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:37:44 +02:00
RichardandClaude Opus 5 f74e49ba35 Freigabe-Hygiene (#631, #579, #609, #610) und JSON-Filter fuer WebFetch (#632)
#631: Derselbe Vorschlag (gleicher Agent, gleiches Werkzeug, gleiche Argumente unabhaengig
von Schluesselreihenfolge) wird nicht erneut angelegt - der Agent erfaehrt die bestehende
Nummer. Offene Vorschlaege verfallen nach 24 h (Status Expired).

#609: Werkzeuge koennen den Freigabeschluessel aus dem Inhalt bestimmen
(IAgentTool.StagingAktion). Database: SELECT/SHOW/DESCRIBE -> query, INSERT/UPDATE/DELETE
-> write, DDL -> schema; write und schema sind freigabepflichtig. Ausgefuehrt wird nach
Statement-Art, schreibende Statements melden die Zeilenzahl.

#579: Database-Vorpruefung (Zugriffsebene, Tabellenfreigabe, SqlGuard) - was das Werkzeug
ablehnen wuerde, kommt nicht mehr in die Freigabeliste.

#610: SqlGuard ueberspringt IF [NOT] EXISTS und erkennt DESCRIBE/DESC <tabelle>.

#632: WebFetch fetch mit filter liefert aus JSON/JSONP nur passende Eintraege; beim
Blaettern durch grosses JSON empfiehlt der Hinweis den filter.

Version 0.1.10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 18:51:06 +02:00
RichardandClaude Opus 5 f5731c50f8 Budgetsperre sichtbar machen (#608), Gedaechtnisabruf kuerzt statt wegzulassen (#607)
#608: Ein wegen erschoepften Budgets abgelehnter Lauf hinterlaesst jetzt einen Beleg
(Status BudgetExceeded) an Aufgabe und Verlauf; die Log-Warnung kommt einmal je Agent,
Grenze und Tag. Die Kostenprognose zaehlt Ablehnungen nicht als Lauf und warnt, wenn die
geplanten Aufgaben eines Agenten sein Tagesbudget uebersteigen (API + Verbrauchsansicht).

#607: Passt schon der erste Treffer nicht in den Abruf, wird er gekuerzt (Anfang und Ende
bleiben) statt ganz zu fehlen. remember weist auf gleichnamige Eintraege ohne Schluessel und
auf Eintraege nahe der Abrufgrenze hin.

Version 0.1.9.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 18:59:14 +02:00
RichardandClaude Opus 5 779077a347 Kompaktierung: kalibriert pruefen, mit Abstand kompaktieren, Zusammenfassung verbuchen (#605)
Im Dauerbetrieb lief die Kompaktierung bei jedem Schritt an und blieb wirkungslos:
Stufe 1 pruefte mit der rohen Schaetzung, kompaktierte nur knapp unter die Schwelle
und brach dabei jedes Mal den Prompt-Cache; der Zusammenfassungs-Aufruf fehlte im
Verbrauch. Ziel jetzt min(Schwelle, 55 % des Limits), Usage des Aufrufs geht in den Lauf.
Version 0.1.8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:37:41 +02:00
RichardandClaude Opus 5 7dbcc44226 WebFetch Stufe 1: Hauptinhalt als kompaktes Markdown, Abschnitte statt Abschneiden (#602)
- HtmlExtraktor (AngleSharp): Selektor > article (>= 40 % des Seitentexts) > main > Textdichte,
  Seitenrahmen und Stoerbloecke entfernt, Ueberschriften/Listen/Tabellen/Code als Markdown,
  Linkziele nur mit links=true, Metadaten (Titel, Sprache, kanonische URL, Datum)
- WebInhalt: JSON/Text/XML unveraendert, Rueckfall auf Volltext bei zu wenig Hauptinhalt,
  mode=full, nextOffset fuer lange Inhalte, JSON kompakt ohne Unicode-Escapes
- RSS: limit, Beschreibungen gekappt; dataAsOf nicht mehr aus dem Date-Header
Gemessen: tagesschau-Artikel 16.000 (abgeschnitten) -> 9.635 Zeichen vollstaendig.
Version 0.1.7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 12:19:27 +02:00
RichardandClaude Opus 5 0b6ea73ec1 Entscheidungen aus dem Testbetrieb umgesetzt (#572): #577 #579 #581 #589 #193 #205 #594 #595 #597 #598 #599 #588
- Engine: leere Antworten nachfassen, Aufgaben enden mit Ergebniszeile (EmptyResponse/Incomplete),
  gleiche Fehlschlaege erkennen (RepeatedFailure), Zeitzeile vor Chatnachrichten,
  Werkzeugergebnisse gerahmt, Vorpruefung vor dem Staging, Belegkosten wie RunUsage
- Staging: gescheiterte Freigaben als Failed; AgentEditor-Aenderungen freigabepflichtig,
  Selbstbearbeitung gesperrt (allowSelfEdit)
- Taskboard: Termine beim Anlegen pruefen, naechster/letzter Lauf, pausieren, Sofortlauf,
  Kostenprognose, Hinweis auf wachsenden Kontext; Jobs-Reiter und /api/jobs entfernt
- Release: Instanzmanager samt Weboberflaeche im Paket, setup.json fragt manager.json ab,
  Token nicht mehr ins systemd-Journal
Version 0.1.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 11:23:10 +02:00
RichardandClaude Opus 5 8ab9d99abe Cron: Bereich mit Schritt (7-22/3) lesen, ungueltige Ausdruecke im Scanner melden (#597)
Der Nachrichten-Digest im Dauerbetrieb der Test-VM lief nie: CronExpression
verstand 'von-bis/schritt' nicht, TaskSchedule verschluckte den Fehler.
Version 0.1.5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 15:51:36 +02:00
RichardandClaude Opus 5 6581fd0187 Befunde aus dem Testbetrieb auf der Linux-VM behoben (#572)
- #578: Abgebrochene Laeufe (Schrittgrenze, Zeitueberschreitung, Abbruch,
  Ausnahme) tragen ihre bezahlten Token - vorher 0 in RunUsage, fuer das
  Budget unsichtbar.
- #580: WebFetch prueft die aufgeloeste Zieladresse beim Verbinden; ein
  erlaubter Name, der auf 127.0.0.1 aufloest, wird gesperrt.
- #575: Dateien entstehen unter Unix mit 0600 (AtomicFile, Protokolle,
  FileRW-Anhaengen, state.db), Protokollverzeichnisse mit 0700.
- #576: SIGTERM/SIGINT beenden auch mit Fenster geordnet.
- #593: OpenRouter-Statusdienst wird gestartet.
- #589: Auftragsnachricht nennt aktuelle Zeit, Zeitzone und Termin.
- #581: Unvollstaendiges Argument-JSON erreicht das Werkzeug nicht mehr;
  das Modell bekommt eine verstaendliche Rueckmeldung statt Parsertext.
- #591: Weboberflaeche liest die Felder, die API und Ereignisstrom liefern
  (Freigaben, Protokoll, Taskboard mit Termin, Werkzeug-Einstellungen,
  Tokennutzung); Ereignisstrom in camelCase.
- #592: Chat zeigt Nachrichten nicht mehr doppelt.
- #536 (teilweise): A2A-Antworten tragen Absender und Empfaenger, Parser
  wirft nicht mehr; Datums- und Volltextfilter in Agenten-Chats.
- deploy/publish.py: Packager-Pfad net10.0, Schalter --agent-file.
- Version 0.1.4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 15:40:56 +02:00
RichardandClaude Opus 5 b568a45de3 Wiederaufnahme nach Absturz, Nachschau und Auswertung (#553-#556)
#553 FileRW schreibt atomar. Die Vorkehrung lag seit #241 im selben
Repository und wurde ueberall benutzt - Einstellungen, Sicherungen,
Chat-Zustand, Identity.md - nur der Weg, ueber den Agenten ihre
Arbeitsergebnisse ablegen, ging daran vorbei. Damit stand das Verhaeltnis
auf dem Kopf: geschuetzt war das Eigene, ungeschuetzt das Ergebnis.
append bleibt bewusst nicht atomar; das ginge nur, indem die ganze Datei
neu geschrieben wird, und bei einer grossen Protokolldatei waere das der
falsche Handel.

#554 Wiederaufnahme. Neuer Eintrag AuditStatus.Started, geschrieben VOR
der Wirkung und als einziger nicht "best effort" - auf ihn stuetzt sich
alles Weitere, also laeuft der Aufruf gar nicht erst, wenn er nicht
durchkommt. Neue Kennzeichnung IsRepeatable, getrennt von IsParallelSafe:
Das eine heisst "stoert keinen gleichzeitigen Aufruf", das andere
"richtet zweimal keinen Schaden an". CrashRecovery sichtet beim Start,
was offen blieb, und stuft ein - fuehrt aber nichts aus. Es sichtet und
berichtet; entscheiden soll der Aufrufer.

Dabei kippte eine alte Regel: Bisher wurde ein Abbruch bewusst NICHT
protokolliert. Das war richtig, solange es keinen Eintrag vor dem Aufruf
gab. Jetzt saehe sonst jeder Benutzerabbruch beim naechsten Start aus wie
ein Absturz - bis niemand mehr hinsieht. Deshalb AuditStatus.Cancelled.

#555 Optionale Zustandspruefung IToolStateProbe, umgesetzt in FileRW.
Unknown ist eine vollwertige Antwort, kein Fehlschlag: Ob ein SMTP-Server
angenommen hat, laesst sich nicht feststellen, und ein Werkzeug, das hier
raet, waere schlimmer als eines, das schweigt. Partial gilt ausdruecklich
nicht als geklaert - einen halb angehaengten Text sollte niemand
automatisch geraderuecken.

#556 Auswertung. ListForAgentAsync und SummarizeToolUsageAsync. Die Dauer
wird nur ueber abgeschlossene Aufrufe gemittelt; zaehlten die
Started-Eintraege mit ihrer Dauer 0 mit, saehe jedes Werkzeug doppelt so
schnell aus, wie es ist.

Nebenbei: SqliteStorage kann jetzt Spalten nachruesten. CREATE TABLE IF
NOT EXISTS allein genuegte nicht - bei einer bestehenden Datenbank tut die
Anweisung nichts, und erst das naechste INSERT waere gescheitert, im
Betrieb beim Anwender.

Vier Audit-Tests angepasst: Ein ausgefuehrter Aufruf erzeugt jetzt zwei
Eintraege statt einem. Ein Aufruf, der gar nicht erst laeuft - verweigert,
unbekannt, vorgelegt - hat weiterhin genau einen; der Beginn wird erst
nach Rechtepruefung und Staging geschrieben.

23 neue Tests, 912 gesamt, keine Regression. Build ohne Warnungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 21:48:18 +02:00
RichardandClaude Opus 5 fb4b218e45 Abbruch hinterlaesst keinen unbeantworteten Werkzeugaufruf mehr (#552)
Wurde ein Chat abgebrochen, waehrend ein Werkzeug lief, blieb im Kontext
eine assistant-Nachricht mit tool_calls stehen, auf die keine Antwort
folgte. Diese Folge lehnt die Schnittstelle mit HTTP 400 ab - der Agent
war danach nicht mehr ansprechbar.

Der Schaden wurde dabei dauerhaft gemacht: Jeder catch-Block ruft
AddChatEntry, das ruft PersistChatState, und das schreibt den Kontext
atomar auf die Platte. Das atomare Schreiben war hier keine Rettung,
sondern die Garantie, dass die kaputte Fassung heil ankommt. Ein Neustart
half nicht, weil LoadPersistedChats sie zurueckholte.

Ausloeser war durchweg Normalbetrieb: AbortChat durch den Benutzer,
LoopGuard.Timeout, oder eine Instanz, die waehrend eines Werkzeuglaufs
geordnet herunterfaehrt.

Die Antworten gehen jetzt aus einem finally raus. Auf jedem Ausgangspfad
bekommt jeder Aufruf seine Antwort, bei Abbruch eine Ersatzantwort, die
auch sagt, was passiert ist - eine leere waere formal gueltig, liesse das
Modell aber raetseln.

Zwei Tests: einer auf die Form des Kontexts, einer auf das, worauf es
ankommt - dass ein zweiter Chat danach wieder durchlaeuft. Der erste
schlug vor der Behebung fehl.

543 Tests in Core, keine Regression. Build ohne Warnungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 13:39:43 +02:00
RichardandClaude Opus 5 ea3844006f Harness gegen stillen Verlust gehaertet (#547-#551)
Fuenf Befunde mit einem gemeinsamen Fehlermodus: Keiner erzeugt eine
Fehlermeldung. Der Lauf wird fertig, das Ergebnis sieht plausibel aus,
und nur die Qualitaet ist schlechter.

#547 Der Begleittext zum Werkzeugaufruf ging verloren. AssistantWithToolCalls
baute eine neue Nachricht aus nur den Aufrufen; der Text, den das Modell
daneben geschrieben hatte, fiel weg. Damit verlor das Modell zwischen zwei
Schritten seinen eigenen Plan, und im Audit stand, dass ein Werkzeug lief,
nicht warum.

#548 finish_reason wurde nirgends gelesen. Eine an der Ausgabegrenze
abgeschnittene Antwort galt als fertiges Ergebnis. Das trifft besonders
Laeufe im Hintergrund, wo niemand zusieht. Neuer Status Truncated: Das
Teilergebnis geht weiter, aber gekennzeichnet.

#549 reasoning_details wurde verworfen. Bei Werkzeugnutzung muss der
Denkverlauf unveraendert zurueck, sonst beginnt ein Thinking-Modell jedes
Mal von vorn. Wird jetzt als rohes JSON durchgereicht - ohne Auslegung,
weil Reihenfolge und Signatur daran haengen.

#550 Die Anfrage verlangt jetzt einen Anbieter, der alle mitgeschickten
Parameter beherrscht. Ohne das durfte ein Anbieter cache_control, reasoning
oder sogar tools stillschweigend uebergehen - der Lauf lieferte dann eine
gewoehnliche Textantwort, wo ein Werkzeugaufruf hingehoert haette. Dazu
usage.include: der abgerechnete Betrag schlaegt die Eigenrechnung, die als
Rueckfallebene bleibt. Denktiefe je Agent einstellbar, Vorgabe off.

#551 Werkzeuge eines Zuges laufen gleichzeitig, sofern sie sich dafuer
freigeben. Die Vorgabe von IsParallelSafe ist false, weil die falsche
Antwort hier nicht auffaellt: Zwei schreibende Aufrufe auf derselben Datei
scheitern nicht zuverlaessig, sondern gelegentlich. Freigegeben ist bisher
nur WebFetch - es liest ausschliesslich. Die Reihenfolge der Ergebnisse
folgt den Aufrufen, nicht der Fertigstellung; daran haengen die
tool_call_id-Zuordnung und der Cache-Praefix.

27 neue Tests, 887 gesamt, keine Regression. Build ohne Warnungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 13:34:36 +02:00
RichardandClaude Opus 5 fb185ca84d Sicherungen mit dem Lizenzschluessel verschluesselt, optional per FTP (#530)
Eine Sicherung des Managers ist jetzt als Ganzes mit dem Lizenzschluessel
verschluesselt. Beim Zurueckspielen wird der eigene Schluessel probiert; passt er
nicht, fragt die Oberflaeche nach dem Lizenzschluessel der Quellinstallation.

Der Lizenzschluessel ist der eine Wert, den jede Installation ohnehin hat, und er
liegt im Manager, nicht neben den Archiven. Damit faellt der Grund weg, aus dem die
taegliche Sicherung bisher keine Zugangsdaten enthielt: Eine Passphrase haette
gespeichert werden muessen, und neben den Sicherungen waere sie wirkungslos
gewesen. Jetzt reisen die Zugangsdaten mit, und eine Sicherung uebersteht den
Maschinenwechsel ohne Passphrase.

LicenseBackupCipher: eigener Behaelter .clawdbak. Kopf mit Schluesselprobe, das
Manifest als eigener Abschnitt, das ZIP in 1-MiB-Stuecken mit AES-256-GCM und
Endmarke. PBKDF2 aus Lizenzschluessel und festem Salt, der Schluessel normiert.
Kein RAR (braucht das lizenzpflichtige Herstellerprogramm), kein ZIP mit Kennwort
(kann .NET nicht schreiben, ZipCrypto ist gebrochen, und mit abgeleitetem
Schluessel braechte 7-Zip-Vertraeglichkeit nichts).

Zurueckspielen: Die Schluesselprobe laeuft VOR dem Anhalten der Instanz. Passt
der Schluessel nicht, antwortet der Endpunkt mit 422 und dem Code fremde_lizenz,
und ein Fenster verlangt den Schluessel der Quelle.

ManagerBackups: Gefuehrte Instanzen sichern sich nicht mehr selbst, weil nur der
Manager den Schluessel hat; er liest ihre Einstellungen aus InstanceSettings.json.
Einmal am Tag auch ueber einen Neustart des Managers hinweg. Ohne Lizenzschluessel
wird nichts gesichert, auch nicht ersatzweise unverschluesselt.

FTP (FluentFTP, schon im Loesungsbaum): TLS wo angeboten, requireTls erzwingt es;
Upload unter .part-Namen und danach umbenannt; Aufraeumen auf dem Server je
Instanz; ein gescheiterter Upload laesst die lokale Sicherung stehen. Das
Kennwort liegt verschluesselt in manager.json und geht nie an die Oberflaeche.

Welche Datei zu welcher Instanz gehoert, entscheidet ein Zeitstempel nach dem
Namen und nicht das Praefix - Test und Test_2 werden nicht mehr verwechselt.

Grenze, bewusst und dokumentiert (Konzept 9.10): Wer Lizenzschluessel und Archiv
hat, hat die Zugangsdaten. Der Schluessel ist dem Hersteller bekannt und gilt fuer
alle Instanzen einer Installation.

Nicht ueber die Oberflaeche abgenommen: Fenster fuer die fremde Lizenz und FTP
gegen einen echten Server stehen als Block H in #537.

860 Tests gruen, 0 Warnungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 11:03:25 +02:00
RichardandClaude Opus 5 99f8a89fd0 Schluesselbund: ein Hauptschluessel, mehrfach verpackt (#190)
SecretKeyStore.Wrap band den Hauptschluessel unter Windows per DPAPI ans
Benutzerkonto und gab ihn sonst ungewrappt zurueck - eine Zeile, die man beim
Lesen ueberspringt und die auf dem Zielsystem "im Klartext" bedeutet.

Dahinter steht jetzt ein SecretKeyRing: derselbe Hauptschluessel, mehrfach
verpackt, je Fach ein ISecretProvider (dpapi, passphrase, file). Fuer Aufrufer
aendert sich nichts - GetOrCreateKey gibt weiterhin 32 Byte zurueck. Genau dafuer
war die Naht da.

WARUM MEHRERE FAECHER UND NICHT EIN BESSERES
Ein Schluessel an der Maschine ueberlebt keinen Umzug; einer an einer Passphrase
verlangt bei jedem Start jemanden, der tippt - und Dauerbetrieb ohne Menschen ist
der Zweck (#214). Der Bund entscheidet sich nicht: Im Alltag oeffnet das
Maschinenfach ohne Rueckfrage, nach einem Umzug die Passphrase, und beide fuehren
zu DEMSELBEN Schluessel. Alle enc:v2-Werte bleiben lesbar.

Und deshalb darf die Schluesseldatei jetzt ueberhaupt in eine Sicherung: Sobald
ein Wiederherstellungsfach da ist, steht der Hauptschluessel in keiner Fassung im
Klartext darin.

Nicht zu verwechseln mit der Sicherung mit Passphrase: Die schluesselt jeden WERT
einzeln ins Archiv um, fuer den Transport einer Instanz. Das Wiederherstellungs-
fach schluesselt den HAUPTSCHLUESSEL um, damit eine bestehende Installation
weiterlaeuft - unbeaufsichtigt ueber CLAWD_SECRET_PASSPHRASE. Die Unterscheidung
steht in docs/Roadmap.md bei #190.

Beide Altformate werden uebernommen: 32 rohe Byte werden zum Fach file, ein
DPAPI-Block zum Fach dpapi. Derselbe Schluessel, nur anders verpackt.

Zwei Zusagen, mit Tests festgehalten:
- Geht KEIN Fach auf, wird abgebrochen statt ein neuer Schluessel erzeugt. Ein
  neuer Hauptschluessel machte jeden bestehenden Wert wortlos unlesbar.
- Ein Umzug legt den Schluessel NICHT im Klartext ab, nur damit der naechste Start
  bequemer wird.

Beim automatischen Weg wird nur hinzugefuegt, nie weggenommen; weggenommen wird
nur in SetRecoveryPassphrase, wo ein Mensch gerade getippt hat.

#190 bleibt offen: Unter Linux greift weiterhin das Fach file. Neu ist, dass es
beim Start als Warnung im Protokoll steht statt unsichtbar zu sein. Womit das
Maschinenfach dort gefuellt wird, ist Entscheidung #180 - diese Schnittstelle ist
die Stelle, an der die Antwort eingesetzt wird.

834 Tests gruen, 0 Warnungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:34:16 +02:00
RichardandClaude Opus 5 e3403087c2 Handschlag: --managed hebelt die Lizenzpruefung nicht mehr aus (#535)
Mit E-M3 wanderte die Lizenzpruefung in den Manager: Eine gefuehrte Instanz prueft
nicht mehr selbst, weil der Manager es zentral getan hat. Woran sie "gefuehrt"
erkannte, war der Schalter --managed - und ein Schalter ist keine Erkennung.
ClawdDotNet --managed startete damit eine Instanz ganz ohne Lizenzpruefung. Vor
E-M3 gab es die Luecke nicht; sie ist mit der Zentralisierung entstanden.

ManagerHandshake unterschreibt Instanzverzeichnis und Ablauf mit HMAC-SHA256. An
die Signierstelle kommt der Manager nur, wenn seine Lizenzpruefung bestanden hat -
StartInstanceAsync weist vorher mit StartRefusal.NoLicense ab. Dass die
Unterschrift existiert, ist der Beweis.

- Gebunden an das Verzeichnis, und geprueft wird gegen das tatsaechliche, nicht
  gegen das behauptete: Sonst unterschriebe man das Argument statt der Sache.
- Fuenf Minuten gueltig, der Ablauf geht in die Unterschrift ein.
- Ueber die Umgebung statt ueber die Befehlszeile: Eine Befehlszeile steht in
  jeder Prozessliste.
- Ungueltig heisst "nicht gefuehrt", nicht Abbruch. Dann prueft die Instanz ihre
  Lizenz selbst - der Weg von vor E-M3, also kein neuer Fehlerfall.

AppHost.IsManaged ist jetzt der gepruefte Befund. CanSwitchInstance und der
Wechseldialog haengen daran statt am Schalter.

Die Grenze steht ausdruecklich im Konzept (9.9): Der Schluessel liegt in jeder
ausgelieferten Programmdatei. Es geht um den gewoehnlichen Weg an der Lizenz
vorbei, nicht um jemanden mit Werkzeugen und Willen.

Abgenommen an echten Prozessen, Unterschrift fuer den Positivfall unabhaengig in
Python gerechnet: ohne Unterschrift "Lizenz erforderlich"; mit gueltiger kommt die
Instanz hoch; Unterschrift fuer ein fremdes Verzeichnis und hochgedrehter Ablauf
scheitern beide.

820 Tests gruen, 0 Warnungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:11:49 +02:00
RichardandClaude Opus 5 dcffcdcff5 Instanzmanager: Weboberflaeche kennt Instanzen, geordnetes Ende, Puls (#528, #531)
Weboberflaeche (#531): X-Instanz an der einen fetch-Stelle, Instanzauswahl in der
Kopfzeile, Verwaltungsansicht mit Anlegen/Starten/Anhalten/Fenster, Herkunft der
Ereignisse, Sicherungs- und Einstellungsansicht. 402/409/502 werden eigen behandelt.

Sicherungen im Manager (#530): Auflisten, Anlegen, Wiederherstellen, Loeschen. Das
Zurueckspielen haelt die Instanz vorher an und startet sie danach wieder.

Geordnetes Ende (Konzept 9.8). Zwei Befunde waren der Anlass:

Der Manager konnte eine Instanz MIT FENSTER nie geordnet beenden.
Control.ShutdownRequested war nur in HeadlessRun verdrahtet, nicht in der
Avalonia-Anwendung - und ein Fenster ist die Vorgabe. Die Bitte wurde mit 202
beantwortet, niemand hoerte zu, und der Manager schoss den Prozess nach Ablauf
seiner Frist jedes Mal ab.

InstanceSupervisor.StopAsync hielt Socket-Schweigen fuer einen Todesnachweis und
meldete "beendet", ohne den Prozess anzufassen. Bei einer verstummten Instanz ist
der Socket von vornherein still - der Neustart haette einen zweiten Prozess auf
dieselbe state.db gesetzt. Genau der Schaden, gegen den #528 gebaut ist.

- ShutdownCoordinator: erst die Tuer schliessen, dann auf offene Arbeiten warten,
  Frist zwei Minuten. Vorher brach TaskScanner.DisposeAsync mitten in ProcessAsync
  ab, sodass die Aufgabe bis zum Lease-Ablauf auf "running" stehenblieb.
- GET /api/instanz/puls hinter dem Socket-Gate: Phase und offene Arbeiten. Weiter
  gefragt statt gemeldet - ein Puls, den die Instanz von sich aus schickt, fehlt
  genau dann, wenn sie haengt.
- Eskalationsleiter: drei Durchgaenge ohne Antwort, einmal um ein Ende bitten,
  nach 30s beenden und den Vollzug abwarten, gemeldet als Absturz.
- EnsureDeadAsync vor jedem Start.

Weiteres aus dem Review:
- .gitignore verankert: "Instances/" passte auch auf src/ClawdDotNet.Core/Instances/.
  InstanceEndpoint.cs und InstanceLayout.cs waren dadurch nie eingecheckt.
- Das WebUI-Token stand im Protokoll des Managers. Konsole ja, Datei nein.
- Ein Klick auf "Instanz anlegen" loeste requestSubmit() zweimal aus.
- [Obsolete] auf WebServerPort wieder entfernt: sechs Warnungen, die niemand
  abstellen konnte, solange E-M5 nicht fertig ist.
- Die Wiederherstellung bricht nicht mehr ab, wenn der Browser weggeht.
- Entwicklungspfade zum Instanzmanager aus Segmenten statt mit Rueckstrichen, und
  der Port kommt aus manager.json statt fest aus der Zeile.

Offen und gemeldet: --managed hebelt die Lizenzpruefung aus (#535).

806 Tests gruen, 0 Warnungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 11:15:59 +02:00
RichardandClaude Opus 5 7d4e8b3096 Autostart durch Manager, mit Sicherungsautomat (#528)
Entschieden vom Benutzer am 2026-09-08. Der Schalter heisst
autostartByManager und steht in InstanceSettings.json - bei der Instanz,
nicht beim Manager.

ZWEI DINGE IN EINEM SCHALTER, weil es dieselbe Zusage ist: Der Manager
startet diese Instanz, wenn er selbst hochkommt, und er startet sie wieder,
wenn sie abstuerzt. Ohne den Schalter ruehrt er nichts an - ein Manager,
der nach einem Neustart der Maschine alles hochfaehrt, was er findet,
startet auch das, was jemand mit Absicht angehalten hat. Ausgeschaltet
vorbelegt.

DER SICHERUNGSAUTOMAT ist der eigentliche Inhalt. Wieder hochzufahren ist
die einfache Haelfte; die schwierige ist zu erkennen, wann man es lassen
muss. Nach drei Neustarts in zehn Minuten (maxRestartsInWindow,
restartWindowMinutes) setzt der Manager aus und meldet es. Die Instanz
bleibt dann aus, bis jemand sie von Hand startet - und dieser Start raeumt
den Zaehler ab, denn wer von Hand startet, hat entweder etwas geaendert
oder weiss, was er tut. Das Zeitfenster wandert: Eine Instanz, die einmal
im Monat abstuerzt und wieder hochkommt, laeuft nie in die Grenze.

Nur bei einem Absturz, nicht wenn eine Instanz verstummt. Eine stumme
Instanz lebt noch; sie neu zu starten hiesse, sie erst abzuschiessen, und
ob der Manager das von sich aus darf, ist nicht entschieden. Als offene
Frage aufgeschrieben - genau dieser Zustand war der Anlass des Vorhabens.

Der Schalter steht bei der Instanz, weil er eine Eigenschaft dieser Instanz
ist und weil die Instanzdatei dem Instanzprozess gehoert: Sie vom Manager
schreiben zu lassen, waehrend die Instanz laeuft, waeren zwei Schreiber auf
einer Datei. Der Manager liest ihn nur; geaendert wird er auf der
Einstellungsseite der Instanz.

EIN DEFEKT, den erst diese Tests gezeigt haben: StartAsync gab "laeuft
schon" zurueck, wenn die Aufsicht die Instanz fuer laufend hielt - ohne
nachzusehen. Zwischen zwei Aufsichtsdurchgaengen liegen aber Sekunden, und
in denen kann sie wegbrechen. Wer in dieser Luecke "starten" verlangte,
bekam einen Erfolg, hinter dem nichts stand, und der naechste Durchgang
meldete einen Absturz, den niemand mehr erwartete. Jetzt wird gefragt,
bevor zugestimmt wird.

UND EIN TESTDOUBLE, das die Wirklichkeit nicht abbildete: Der falsche
Socket antwortete unabhaengig davon, ob ein Prozess lief. Damit war jeder
Testaufbau, der die Antwort vor dem Hochfahren hinterlegte, stillschweigend
ein anderer Test als beabsichtigt - eine Antwort sagt dem Manager "laeuft
bereits", er uebernimmt, und der Starter wird nie angefasst. Dreimal
passiert. Der falsche Socket haengt jetzt am falschen Starter.

Sicherungen im Manager sind als #530 zurueckgestellt, mit Ausloeser.
Vorab entschieden ist dort nur eines: Zurueckspielen ausschliesslich bei
inaktiver Instanz.

19 neue Tests. Build ohne Warnungen, 793 Tests gruen, 8 uebersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 00:01:52 +02:00
RichardandClaude Opus 5 db7abf699f Instanzmanager Stufe 5: Wechseln heisst nach vorn holen (#528, E-M2)
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>
2026-09-08 23:39:50 +02:00
RichardandClaude Opus 5 a511480b77 Instanzmanager Stufe 4: eine Adresse, ein Zugang, alle Instanzen (#528, E-M5)
Der Managerprozess. ClawdDotNet.Instanzmanager ist eine Konsolenanwendung:
Lizenz pruefen, uebernehmen was laeuft, Weboberflaeche aufhaengen, warten.
Beim Herunterfahren beendet er keine Instanz.

Zwei neue Projekte. ClawdDotNet.Manager.Web traegt die Weboberflaeche -
ohne jeden Verweis auf ClawdDotNet.Web, denn dort haengt App und daran
sechzehn Werkzeugprojekte. ClawdDotNet.ManagerApp ist der Einstiegspunkt.
wwwroot ist aus der Instanz in den Manager gezogen.

DER SCHNITT DER HTTP-FLAECHE: Unter /api/verwaltung steht, was den Manager
angeht - auflisten, anlegen, starten, anhalten. Alles andere unter /api
geht unveraendert an eine Instanz. Er leitet weiter, er implementiert
nicht. Welche Instanz gemeint ist, sagt die Kopfzeile X-Instanz; laeuft
genau eine, ist sie gemeint, und laufen mehrere, wird nachgefragt statt
geraten - eine Anfrage an die falsche Instanz ist schlimmer als eine
abgelehnte.

Der zusammengefuehrte Ereignisstrom haelt je laufender Instanz eine
Zuleitung und schreibt an jedes Ereignis, von wem es kommt. Ergaenzt statt
verpackt: Ein Umschlag waere einfacher gewesen, haette aber jede Ansicht
der Weboberflaeche zum Umschreiben gezwungen.

ABNAHME gegen laufende Prozesse: 401 ohne Token, Oberflaeche ohne Token
200, Instanz ueber den Manager gestartet und angehalten, /api/info und
/api/status unverfaelscht durchgereicht, /events/logs mit
"instanz":"Instance-Test" an jeder Zeile. Manager abgeschossen: Instanz
laeuft weiter. Manager neu gestartet: "1 Instanzen bekannt, 1 laufen
bereits" - uebernommen. E-M6 damit in beide Richtungen belegt.

DREI DEFEKTE, die erst der Lauf gezeigt hat:

1. Der Manager hing an einer Konsolenabfrage nach dem Lizenzschluessel. Ein
   Test auf Console.IsInputRedirected reicht dafuer NICHT: Er beantwortet
   "ist die Eingabe umgeleitet", nicht "kann jemand antworten" - ein
   Prozess ganz ohne Konsole, der gefaehrlichste Fall, ist nicht
   umgeleitet. Der Manager fragt jetzt gar nicht.
2. Eine unlesbare manager.json wurde stillschweigend durch Vorgaben
   ersetzt: falsches Instanzverzeichnis, anderer Port, Lizenzfrage - und
   nichts sagte, woran es lag. LoadProblem meldet den Grund jetzt.
3. Der Instanzprozess erbte CLAWD_CONFIG_DIR vom Manager und legte sich in
   dessen Konfigurationsverzeichnis eine eigene Settings.json an. Eine
   Umgebungsvariable vererbt sich, ein Argument nicht - der Manager nimmt
   sein Verzeichnis jetzt ueber --config.

SharedLoggerProvider ist nach Core gezogen: Es gibt jetzt zwei Webhosts,
die dasselbe brauchen, und sie duerfen einander nicht kennen.

NOCH OFFEN in dieser Stufe: Die Weboberflaeche kennt noch keine Instanzen -
Instanzauswahl, X-Instanz und die Verwaltungsansicht sind Arbeit an
wwwroot. Und webServerPort steht noch in der Instanz.

Build ohne Warnungen, 775 Tests gruen, 8 uebersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 23:19:49 +02:00
RichardandClaude Opus 5 cb56175d20 Instanzmanager: Lizenz und Sub-Token wandern in den Manager (#528, E-M3)
Neues Projekt ClawdDotNet.Licensing. LicenseGate, LicenseWatch, LicenseInfo
und ILicensePrompt sind aus ClawdDotNet.App dorthin gezogen, weil zwei
Prozesse dieselbe Lizenzpruefung brauchen und keiner von beiden den anderen
kennen darf. In Core konnte es nicht landen - dann zoege jedes der sechzehn
Werkzeugprojekte das Deploymentcenter-SDK mit.

Dabei ist die Kopplung an SettingsManager verschwunden. LicenseGate kennt
jetzt nur ILicenseStore: Serveradresse lesen, Schluessel lesen und
schreiben, speichern. Die Anwendung fuehrt ihn in Settings.json, der
Manager in manager.json. Der Torwaechter prueft Schluessel; Dateien
verwaltet er nicht. Auch die Anwendungsversion kommt als Parameter statt
aus einem globalen Zugriff - diese Schicht weiss nicht, welche Anwendung
sie benutzt.

ManagerCore ist die Klammer: eigene Einstellungen, Lizenz, Register,
Meldeweg. Drei Regeln:

- Ohne gueltige Lizenz startet er keine Instanz. Laufende werden trotzdem
  in die Liste genommen - man soll sie sehen und anhalten koennen.
- Ein Widerruf haelt alle an. Der einzige Fall, in dem der Manager das von
  sich aus tut. Ein Serverausfall gehoert ausdruecklich nicht dazu; das SDK
  trennt Urteil und gescheiterten Versuch, und nur das Urteil kommt hier
  an. Sonst naehme ein Netzproblem saemtliche Installationen mit.
- Ohne hinterlegte Lizenzpruefung sperrt er nichts, sonst gaebe es eine
  Henne-Ei-Sperre, bevor jemand einen Schluessel eintragen kann.

Sub-Token je Instanz: Der Manager haelt das Master-Token und tauscht es
beim ANLEGEN einer Instanz gegen ein eigenes (watchdog:ping,
bugtracker:report, einzeln widerrufbar). Beim Anlegen und nicht beim ersten
Start, weil die Instanzdatei dann zweifelsfrei niemandem gehoert - sie
spaeter zu schreiben, waehrend der Instanzprozess laeuft, hiesse zwei
Schreiber auf einer Datei. Fuer Instanzen, die es vor dem Manager schon
gab, wird es vor dem Start nachgeholt. Danach fragt er ueber whoami nach,
ob das Token an ein Projekt gebunden ist (SDK 2.8.1). Ein
fehlgeschlagener Tokenbezug haelt die Instanz nicht auf.

NOCH NICHT VOLLSTAENDIG: Die Instanzen pruefen die Lizenz weiterhin selbst.
Das laesst sich erst abschalten, wenn eine Instanz feststellen kann, ob ein
Manager da ist - vorher entstuende eine Fassung ganz ohne Durchsetzung.

Und ein Testfehler, der zweimal derselbe war: Die Socket-Antwort war
hinterlegt, BEVOR der Manager hochfuhr. Damit sagt man "diese Instanz
laeuft bereits", der Manager uebernimmt sie, der Starter wird nie
angefasst - und der Test prueft etwas anderes, als er behauptet. Es gibt
jetzt eine Hilfsmethode, die das ausschliesst.

Build ohne Warnungen, 774 Tests gruen, 8 uebersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:54:51 +02:00
RichardandClaude Opus 5 72b0764d1a Instanzmanager: Register, Einstellungen und stellvertretende Meldung (#528)
Der zweite Teil von Stufe 3. Die Aufsicht ueber eine einzelne Instanz stand
schon; jetzt gibt es die Klammer darum und den Weg nach draussen.

InstanceRegistry haelt je Instanz eine Aufsicht. Zwei Zusagen, beide aus
E-M6: Beim Start wird UEBERNOMMEN, nicht gestartet - ein Manager, der beim
Hochkommen alles hochfaehrt, 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 schliesst, will den Manager
schliessen. Einzige Ausnahme ist StopAllAsync fuer einen Grund, der alle
betrifft - und der heisst Lizenzwiderruf.

SupervisionReporter macht aus einem Befund eine Bugtracker-Meldung,
stellvertretend: mit Prozesskennung, Abschiedscode und dem Ausschnitt aus
dem Instanzprotokoll, der noch auf der Platte liegt. Der Idempotenzschluessel
ist stabil je Instanz und Art - stuerzt dieselbe Instanz wiederholt ab,
zaehlt der Server hoch, statt den Bugtracker mit Zwillingen zu fuellen.

Ein leerer Protokollauszug ist dabei die aussagekraeftigste Zeile der
Meldung und wird ausdruecklich als solche geschrieben. Genau das war der
Zustand am 2026-09-08: Der Prozess wurde erschlagen, bevor sein
Schreibpuffer geleert war, und hinterliess nichts.

manager.json traegt die Felder aus Abschnitt 6 des Konzepts und sonst
nichts. Lizenzschluessel, Master-Token und Web-Token liegen darin
verschluesselt und zur Laufzeit im Klartext - dieselbe Bauart wie die
Anwendungseinstellungen, mit Absicht. Unsinnige Aufsichtswerte werden
eingefangen, weil die Datei von Hand bearbeitbar ist.

InstanceLayout ist nach Core gezogen (ResolveLogDirectory,
ResolveBackupDirectory). Der Manager braucht das Protokollverzeichnis einer
Instanz fuer den Auszug in der Meldung und darf ClawdDotNet.App nicht
kennen; dieselbe Rechnung an zwei Stellen zu fuehren waere die schlechtere
Antwort. AppHost leitet nur noch weiter.

EIN DEFEKT, den erst die eigenen Tests gezeigt haben: Eine angehaltene
Instanz erschien im Register als "Instance-Alpha" statt "Alpha". Die
Aufsicht kennt den Namen erst, wenn die Instanz einmal geantwortet hat, und
eine angehaltene tut das nie - waehrend genau diese Ansicht der Manager
anzeigt. Das Register fuehrt den Namen jetzt aus InstanceSettings.json mit.

Und ein Testdouble, das zu viel behauptet hat: Der falsche Socket
antwortete fuer jedes Verzeichnis dieselbe Instanz. Das laesst Tests gruen
werden, die im Betrieb zwei Instanzen verwechseln wuerden; er antwortet
jetzt je Verzeichnis.

29 neue Tests. Build ohne Warnungen, 766 Tests gruen, 8 uebersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:45:22 +02:00
RichardandClaude Opus 5 4ef6d714a8 Instanzmanager Stufe 3: Prozessaufsicht, Start und Stopp (#528)
Der Anlass des ganzen Vorhabens: Am 2026-09-08 hat sich eine Instanz
aufgehaengt und wurde von Windows beendet. Zu diesem Vorgang steht keine
einzige Zeile in den Instanzprotokollen, kein Bugtracker-Eintrag, kein
Watchdog-Alarm - weil Meldeweg und Watchdog in genau dem Prozess lebten,
ueber den sie haetten berichten sollen. Ab jetzt sieht jemand anders hin.

Neu ist ClawdDotNet.Manager mit genau EINEM Projektverweis: Core. Das ist
keine Sparsamkeit, sondern die halbe Architektur - App schleppt sechzehn
Werkzeugprojekte hinter sich her, und "keine Werkzeuge im Manager" soll
eine Eigenschaft des Bauplans sein und keine Absichtserklaerung.

DIE ENTSCHEIDUNG, AN DER ALLES HAENGT: Die Aufsicht fragt den Socket, nicht
die Prozessliste. Die Prozessliste sagt, dass etwas da ist. Sie sagt nicht,
ob es noch arbeitet - und genau das war der Zustand, den niemand bemerkt
hat. Ein Durchgang kennt drei Antworten: Socket antwortet, sie laeuft.
Socket schweigt und der Prozess ist weg, Absturz mit Abschiedscode. Socket
schweigt und der Prozess lebt, verstummt - aber erst nach mehreren
Fehlversuchen in Folge, denn 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
laeuft" und "die Instanz laeuft" liegen Datenbank, Agenten und Kestrel; wer
nur auf Process.Start wartet, haelt eine Instanz fuer gestartet, die gleich
an einer kaputten Konfiguration scheitert.

Angehalten wird durch Bitten. Neu auf der Instanzseite:
POST /api/instanz/beenden, ausschliesslich ueber den Socket - ueber TCP
gibt es den Endpunkt nicht, auch mit gueltigem Token nicht, und er
antwortet dort 404 statt 403. Die Lebensdauer einer Instanz gehoert dem
Manager; eine Browsersitzung soll den Prozess, der sie bedient, nicht
beenden koennen. Ausgeloest wird der Wunsch erst nach dem Absenden der
Antwort (Response.OnCompleted), sonst bekaeme der Manager statt einer
Zusage einen Verbindungsabbruch und hielte ihn fuer einen Fehlschlag.

Wiederfinden nach einem Managerausfall (E-M6): Die Zustandsdatei
instance-state.json ist der Hinweis, der Socket ist der Beweis. Erst wird
gefragt, wer da antwortet; nur bei gleicher Instanzkennung wird die
Prozesskennung aus der Datei angefasst. Andersherum fuehrt eine
wiederverwendete Nummer nach einem Neustart der Maschine geradewegs dazu,
ein fremdes Programm zu beaufsichtigen und beim Anhalten zu beenden.

Selbsttaetiger Neustart ist bewusst NICHT gebaut. Die Abnahmebedingung
lautet "merkt es und meldet es", und zum Neustarten trifft das Konzept
keine Entscheidung; eine Instanz in einer Schleife wieder hochzufahren,
die jedes Mal abstuerzt, waere die falsche Vorgabe fuer etwas, das niemand
entschieden hat. Als offene Frage aufgeschrieben.

ABNAHME, mit echten Prozessen gemessen: Manager startet Instanz Test in
0,7 s, identifiziert sie ueber die Named Pipe, schreibt die Zustandsdatei
(pid 54476, id d68af6fd). Prozess von aussen abgeschossen. Naechster
Durchgang: Crashed, Code -1, meldenswert, Zustandsdatei weggeraeumt. Dazu
20 Tests gegen gesteuerte Prozesse - ein Absturz zum gewuenschten Zeitpunkt
und eine wiederverwendete Prozesskennung sind mit einem echten Programm
nicht herzustellen.

Noch offen in dieser Stufe: Instanzen anlegen, Lizenz im Manager,
Sub-Token je Instanz, und der Managerprozess selbst.

Build ohne Warnungen, 737 Tests gruen, 8 uebersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:32:28 +02:00
RichardandClaude Opus 5 b78e5d844f InstanceDirectoryManager nach Core verschoben (Vorarbeit #528 Stufe 3)
Reiner Umzug, keine Verhaltensaenderung. Der Instanzmanager braucht die
Verzeichnisstruktur einer Instanz - anlegen, auflisten, Konfiguration lesen
-, aber er darf ClawdDotNet.App nicht referenzieren: Dort haengen sechzehn
Werkzeugprojekte dran (Telegram, Mail, FTP, Rocket.Chat, ...), und der
Manager soll ausdruecklich keines davon kennen. Ein Projektverweis wuerde
sie nicht ausfuehren, aber laden - und die Zusage "keine Werkzeuge im
Manager" waere nur noch eine Absichtserklaerung statt einer Eigenschaft des
Bauplans.

Das Wissen selbst gehoert ohnehin nach Core: Es beschreibt, wie eine Instanz
auf der Platte liegt, und InstanceConfig und AgentConfig liegen dort schon.

Verschoben nach Core/Instances: InstanceDirectoryManager, InstanceInfo,
TokenUsageRecord/TokenUsageFile, AgentListItem/AgentListFile. In
ClawdDotNet.App/Models bleiben JobHistoryEntry und die
Werkzeug-Ansichtsmodelle, die dort richtig liegen.

Build ohne Warnungen, 717 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:17:46 +02:00
RichardandClaude Opus 5 373e1f4caf Deploymentcenter-Anbindung auf SDK 2.8.2 nachgezogen (#218)
Der Changelog seit 2.6.0 nennt sechs Fassungen, 16 action_items und fuenf
brechende Aenderungen. Davon trifft fast nichts diese Anwendung - und das
ist ein Befund, kein Versaeumnis:

- finding_status (brechend, 2.7.0): kein Aufrufer. Weder
  /api/supervisor/v1/observe noch /status werden hier angesprochen.
  Vorgemerkt fuer #528 Stufe 3 - wer den Manager baut, liest den
  Befundzustand von Anfang an aus diesem Feld.
- Nachpruefungs-Status awaiting_verification, in_verification,
  fix_insufficient (brechend, 2.6.1): Die Anwendung wertet
  bugtracker_items.status nirgends aus. Betrifft den Arbeitsablauf der
  Agenten, und der steht in CLAUDE.md bereits richtig beschrieben.
- Meldungen ohne project_slug (brechend, 2.8.1): BugtrackerClient und
  ErrorReporter schicken project_slug und build_version seit jeher mit.
- Leseweg ohne project_slug und schmales dc_roadmap (brechend, 2.8.1):
  betrifft den MCP-Adapter, nicht den Anwendungscode.

Geblieben ist die eine Zusage, die man nicht sieht, wenn man sie verletzt:
Seit 2.8.1 erbt ein Watchdog-Monitor das Projekt seines Ping-Tokens. Ohne
Bindung landen die Befunde im Scheinprojekt "default", und eine Abfrage
nach dem eigenen Projekt liefert fuer eine laufende, fleissig meldende
Instanz null Befunde - die Aufsicht sieht aus wie "nichts auffaellig". Das
ist genau die Falle, in die hier schon einmal Meldungen gelaufen sind.

Deshalb neu: TokenProvisioner.WhoAmIAsync gegen GET
/api/tokens/v1/whoami (SDK 2.7.1) und der Typ TokenIdentity mit
IsProjectBound. DeploymentcenterService fragt beim Start das eigene
Melde-Token, protokolliert das Projekt und warnt, wenn keines dransteht.
Bewusst nur eine Warnung: Monitoring, das bei unklarer Rechtelage gar nicht
erst anlaeuft, ist genau dann still, wenn man es braucht. Faellt die
Auskunft aus - aelterer Server, kein Netz -, bleibt es bei Debug.

Gemessen statt geraten: TokenProvisioner schickt keinen project_slug mit,
und ob der Server die Bindung vom Master-Token erbt, steht in keiner
Beschreibung, die vorliegt. Ein Sub-Token dafuer eigens auf dem
Produktivserver auszustellen waere zu viel Nebenwirkung gewesen; die erste
Instanz, die eines zieht, sagt es jetzt selbst ins Protokoll.

#176 ist erledigt und die Regel in CLAUDE.md gestrichen: dc_roadmap ohne
project_slug liefert jetzt das eigene Projekt (nachgemessen: 51 Eintraege,
alle clawddotnet). Wer projektuebergreifend lesen will, schickt
project_slug=all.

dc_link_project auf 2.8.2 gesetzt, CLAUDE.md-Zeile hochgesetzt.

Build ohne Warnungen, 717 Tests gruen, 8 uebersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:14:19 +02:00
RichardandClaude Opus 5 8686d13999 Instanzmanager Stufe 2: Kestrel auf dem Socket, Instanz kopflos (#528)
Eine Instanz ist jetzt ohne Port ansprechbar. InstanceEndpoint liefert die
Adresse: unter Linux <Instanz>/manager.sock, unter Windows eine Named Pipe,
deren Name aus einem SHA-256-Fingerabdruck des Instanzpfades entsteht. Zwei
Instanzen bekommen damit zwangslaeufig zwei Adressen, ohne dass jemand
etwas vergeben muss - genau der Unterschied zur Vorbelegung 7001, um die
sich zwei Instanzen stillschweigend streiten.

Der Socket ist immer da, auch ohne Weboberflaeche. Er gehoert nicht zu
#494, sondern ist die Steuerflaeche der Instanz: webUiEnabled schaltet den
TCP-Zugang, nicht den Socket. WebUiHost heisst deshalb jetzt
InstanceHttpHost - ein Kestrel, zwei Zugaenge.

Ueber TCP gilt Token-Pflicht, ueber den Socket nicht: Dort steht die Grenze
am Dateisystem (0600 unter Linux, CurrentUserOnly bei der Named Pipe,
explizit gesetzt statt auf die Vorgabe vertraut). Wer die Socketdatei
oeffnen darf, sieht ohnehin in das Verzeichnis, in dem alles liegt, was ein
Token schuetzen wuerde.

Woran die beiden Zugaenge auseinandergehalten werden, ist der Punkt, an dem
ein Irrtum teuer waere. Bei Socket-Verbindungen stehen in
HttpContext.Connection keine IP-Adressen - daraus zu schliessen "keine IP,
also Socket" waere eine Vermutung an der Stelle, an der ein Torwaechter
steht, und sie irrt in die falsche Richtung: Im Zweifel liesse sie eine
TCP-Anfrage ohne Token durch. Der Socket-Zuhoerer haengt deshalb jeder
Verbindung selbst eine Marke an (LocalTransport, Verbindungs-
Zwischenschicht ueber ListenOptions.Use); wo sie fehlt, gilt Token-Pflicht.

Ein belegter Port kostet nur noch die Weboberflaeche. Vorher gab TryStart
in dem Fall null zurueck und die ganze HTTP-Flaeche war weg. Seit dem
Socket ist das die falsche Rangfolge: Ihn zu verlieren, weil eine zweite
Instanz laeuft, hiesse die Instanz unerreichbar zu machen - in genau dem
Fall, fuer den es den Manager gibt.

Kopfloser Betrieb ueber --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 ueber das bereits vorhandene
NonInteractiveLicensePrompt, Telegram-Anmeldung aus.

Nachgemessen, nicht angenommen: Kopflose Instanz auf Instance-Test
gestartet, kein Fenster, und eine rohe HTTP/1.1-Anfrage aus einem fremden
Prozess (PowerShell, direkt auf die Pipe) bekam 200 OK auf /api/info, ohne
Token. Das ist die Abnahmebedingung fuer Stufe 2.

Korrektur zur eigenen Ankuendigung: webServerPort entfaellt nicht mit
Stufe 2, sondern mit Stufe 4. Solange kein Manager die Weboberflaeche
traegt, hiesse Wegnehmen, sie ersatzlos abzuschalten.

Zwei Luecken bleiben offen und gehoeren auf die Linux-Testmaschine: Die
Socketdatei-Faelle sind unter Windows gegenstandslos und werden dort mit
sichtbarer Begruendung uebersprungen - der Unix-Domain-Socket selbst ist
auf der Entwicklungsmaschine also ungeprueft, obwohl er die Zielplattform
ist. Und das geordnete Ende auf SIGTERM liess sich hier nicht ausloesen:
Windows kennt das Signal fuer einen fensterlosen Prozess nicht.

Build ohne Warnungen, 714 Tests gruen, 8 uebersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:08:23 +02:00
RichardandClaude Opus 5 facd21459c Instanzmanager Stufe 1: Protokolle und Sicherungen je Instanz (#528)
Mehrere Instanzen sollen unabhaengig voneinander laufen. Heute tun sie das
nicht: Sie teilen sich Protokollverzeichnis, Sicherungsordner und
Sicherungszeitplan. In einer gemeinsamen Startup.log standen abwechselnd
Zeilen zweier Instanzen, ohne Kennzeichnung, welche gerade spricht -- im
Mehrinstanzbetrieb ist damit jede Fehlersuche Ratearbeit. Genau in dieser
Lage ist der Aufhaenger aus #529 entstanden.

Fuenf Schluessel wandern aus AppSettings nach InstanceSettings.json:
logDirectory, backupDirectory, backupKeepCount, autoBackupEnabled,
autoBackupTime. Leer heisst jetzt <Instanz>/Logs beziehungsweise
<Instanz>/Backups; ein relativer Wert zaehlt weiterhin gegen das
Instanzverzeichnis, nicht gegen das Arbeitsverzeichnis des Prozesses
(#375 bleibt gewahrt).

autoBackupEnabled und autoBackupTime standen in der Planung nicht, sind
aber mitgegangen: Ein Schalter, der die taegliche Sicherung fuer eine
Instanz einschaltet und sie damit fuer alle einschaltet, ist derselbe
Fehler wie ein Ordner fuer alle -- und zwei von vier Sicherungsfeldern zu
verschieben hinterliesse eine Einstellungsseite, auf der die Haelfte
anwendungsweit gilt.

Keine Uebernahme der Altwerte, aber eine Ansage: SettingsManager.
AbandonedKeys meldet die aufgegebenen Schluessel, solange sie noch in
Settings.json stehen, und der Start protokolliert einmal, was nicht mehr
gilt und wohin diese Instanz stattdessen schreibt. Ein anwendungsweiter
Pfad, in alle Instanzen kopiert, waere wieder ein Ordner fuer alle.

webServerPort bleibt vorerst stehen. Es entfaellt mit Stufe 2, sobald
Kestrel auf dem Socket hoert; vorher wegzunehmen hiesse, die
Weboberflaeche ohne Ersatz abzuschalten.

Korrektur an der eigenen Befundlage: "Instanz A wirft die Sicherungen von
Instanz B weg" war zu breit. Beide Rotationen filtern bereits nach dem
Praefix backup_<Instanzname>_. Der reale Defekt ist enger, und ein Praefix
ist keine Grenze: backup_Test_2_*.zip faengt mit backup_Test_ an, also
zaehlt Test im gemeinsamen Ordner die Archive von Test_2 mit. Belegt in
BackupSchedulerTests, das den alten Zustand eigens wiederherstellt.

Nachgeprueft an der laufenden Installation, nicht nur im Test: Nach dem
Umbau schreibt Instance-Test nach Instances/Instance-Test/Logs, das alte
gemeinsame Verzeichnis bekommt keine Zeile mehr, und die Warnung zu den
aufgegebenen Schluesseln erscheint genau einmal.

Enthaelt ausserdem, was seit der Weboberflaeche liegengeblieben war:

- #526: Instanzgeheimnisse wurden beim Speichern verschluesselt und beim
  Laden nie entschluesselt. LoadInstanceConfig hatte kein
  ConfigSecrets.Unprotect; ConfigLoader.LoadAsync macht es richtig, wird
  zur Laufzeit aber gar nicht aufgerufen. Der OpenRouter-Schluessel ging
  so verschluesselt an die API.
- Ein offener Ereignisstrom hielt das Beenden fuenf Sekunden auf. Die
  Stroeme hingen nur an RequestAborted, das beim geordneten Herunterfahren
  nicht feuert; jetzt zusaetzlich an ApplicationStopping. Der Fang in
  WebUiHost gilt dazu ohne Bedingung auf RequestAborted -- mit ihr waere
  jeder offene Strom beim Beenden als Fehler im Bugtracker gelandet.
- Nach der Anmeldung blieb die Weboberflaeche leer: #anmeldung
  { display:flex } schlaegt als ID-Selektor das hidden-Attribut.
- docs/Instanzmanager-Konzept.md: Befund, die Entscheidungen E-M1 bis
  E-M6, der Schnitt zwischen Manager und Instanz und die sechs Stufen.

Build ohne Warnungen, 709 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 21:06:40 +02:00
Richard 928da7e6af Standardport fuer WebUI auf 7001 geaendert
- InstanceConfig.WebServerPort von 8080 auf 7001 vorbelegt

- Dokumentation und WebUiOptionsTests nachgezogen
2026-09-08 12:37:57 +02:00
Richard 1e9cb789c8 Weboberflaeche hinzugefuegt und Fachlogik nach App herausgeloest (#493, #494)
- ClawdDotNet.Web: Kestrel im Prozess der Anwendung, Token-Absicherung (TokenGate mit FixedTimeEquals), Endpunkte unter /api, Ereignisstrom unter /events (Server-Sent Events) und fortlaufendes Log-Streaming.

- Weboberflaeche (wwwroot/): Vollstaendiges Single-Page-Frontend mit nativem HTML5, CSS3 und ES-Modulen ohne Buildschritt (strikt textContent, #-Routing, sofortiger Login-Sprung bei fehlendem Token, Light/Dark-Theme, lokale Barlow-Schriften).

- ClawdDotNet.App: Fachlogik aus den Avalonia-ViewModels in Dienste herausgeloest (AgentAdministration, JobAdministration, ServiceAdministration, RunCoordinator) und ueber InstanceChange synchronisiert.

- Tests: ClawdDotNet.Web.Tests und ClawdDotNet.App.Tests hinzugefuegt, alle 684 Tests erfolgreich.
2026-09-07 21:56:02 +02:00
RichardandClaude Opus 5 cffd6f4b77 Bugtracker-Durchgang: 13 Bugs behoben, Build ohne Warnungen
Alle Behebungen auf der Linux-Test-VM gegengeprueft (Ubuntu, .NET 10.0.111):
Build 0 Warnungen / 0 Fehler, 637 Tests gruen, 1 uebersprungen (WindowsFact).

Sicherheit und Zielplattform
  #373 PathBoundary.Canonicalize fiel offen aus. File.ResolveLinkTarget wirft
       fuer einen fehlenden Abschnitt FileNotFoundException; der catch(IOException)
       verliess daraufhin die GANZE Aufloesung mit dem unaufgeloesten Pfad - und
       der besteht jede Praefixpruefung. Der Symlink-Schutz war damit bei jedem
       Schreibvorgang wirkungslos. Jetzt ueber FileSystemInfo.LinkTarget, und
       TryCanonicalize meldet "nicht bestimmbar", statt fail-open durchzureichen.
  #374 WorkspacePath weist den Backslash rundheraus ab, wie den Doppelpunkt.
       Eine Schreibweise statt einer Sonderregel je Windows-Form.
  #375 Log-Verzeichnis: leer heisst "Anwendungseinstellung"; ein relativer Wert
       zaehlt gegen das Instanzverzeichnis, nicht gegen das Arbeitsverzeichnis
       des Prozesses. Ein systemd-Dienst landet nicht mehr auf /Logs.

Agentenkommunikation
  #206 Delegationskette gegen A->B->A (Test A13). Zyklusfrei per Bauart, dazu
       MaxDelegationDepth gegen die Staffel, die niemand beauftragt hat.
  #461 Zwei unabhaengige Ketten ueber Kreuz - beim Beheben von #206 gefunden und
       reproduziert. Ein delegierter Lauf wartet nur noch begrenzt auf ein fremdes
       Gate; ein Chat vom Menschen reiht sich weiter unbegrenzt ein.
  #234 Instanz-Id wird durchgereicht statt aus einem nur beilaeufig gesetzten Feld
       gelesen.

Betrieb und Oberflaeche
  #376 Log-Ansicht chronologisch statt gruppiert je Moduldatei, mit Stacktraces
       an ihrer Meldung - auch ueber Taktgrenzen hinweg.
  #377 Info-Tabelle: feste Spaltenbreiten, damit ein langer Aenderungstext den
       anderen Spalten nicht den Platz nimmt.
  #378 Sicherung: Mindestabstand zwischen Ueberschrift und Schaltflaechenleiste.
  #379 Hellmodus: drei von vier gedaempften Textpinseln lagen unter 4,5:1. Die
       helle Palette ist als Ganzes verschoben, damit die Staffelung sichtbar
       bleibt statt auf einen Wert zusammenzufallen.

Hygiene
  #233 stock_add: der Indexstand wird innerhalb der Sperre festgehalten.
  #235 Zeitumstellung: eine nicht existierende Ortszeit wird auf den ersten
       Zeitpunkt nach der Luecke verschoben statt uebersprungen (R4, R5).
  #236 45 Nullable-Warnungen und eine CA1416 - nicht unterdrueckt, sondern die
       Stellen so umgebaut, dass die Zusage nachvollziehbar wird. Mail meldet
       jetzt, WELCHER Zugangsschluessel fehlt, statt an einer
       KeyNotFoundException zu scheitern.
  #203 Kompaktiert wird vor dem API-Aufruf statt danach; die gemessenen
       Prompt-Groessen kalibrieren die Schaetzung je Modell.

Neu: tests/ClawdDotNet.App.Tests - fuer #375 und #376 gab es keinen Ort fuer
Tests. Dazu Regressionstests fuer jede Behebung; jeder wurde gegen den alten
Stand geprueft, damit belegt ist, dass er den Fehler faengt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 19:04:48 +02:00
RichardandClaude Opus 5 33d95a6c3f UI-Entwurf umgesetzt, Rocket.Chat-Tool, Deploymentcenter 2.4
Sicherungspunkt vor dem Aufraeumen. Buendelt die Arbeit, die seit dem
Abschluss der Avalonia-Portierung im Arbeitsverzeichnis lag.

Oberflaeche
- Entwurf aus Mockup/ umgesetzt: Theme.axaml (Farben je Thema, Barlow als
  mitgelieferte Schrift), Icons.axaml (Symbolgeometrien), Shell.axaml
  (eigene ControlThemes statt Fluent umzufaerben).
- Neue Steuerelemente StrokeIcon und BlueprintFrame, Seiten fuer
  Token-Verbrauch und Agenten-Chats, Werkzeug-Einstellungen als Seite
  statt eigenem Fenster, Texteditor-Fenster.
- ThemeManager mit hellem und dunklem Thema; die beiden Pinsel-Konverter
  entfallen, weil ein fester Farbwert den Themenwechsel nicht ueberlebt.

Rocket.Chat
- Neues Tool-Projekt (Client, Konfiguration, Workspace-Dateien) nach der
  Bauform des Telegram-Tools: rocketchat_poll als Tool-Job, geweckt wird
  nur, wenn wirklich etwas anliegt.
- send_file ist freigabepflichtig, send_message bewusst nicht: Der Raum
  ist Arbeitsraum, der Schutz sitzt an der Raum-Allowlist.
- Konzept-Doc um die Messung gegen die echte Instanz 8.7 ergaenzt; drei
  Annahmen waren falsch und sind korrigiert.

Deploymentcenter
- DC6 (Update anwenden) und DC7 (Erstinstallation ueber setup.json)
  erledigt, DC3 fuer win-x64/dev; deploy/publish.py als Release-Strecke.
- AppHost.DisposeAsync gegen doppeltes Herunterfahren gesperrt - sonst
  ueberschreibt eine zweite Abmeldung den Wartungszustand am Watchdog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:16:05 +02:00
Richard b5bf97ae74 feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar 2026-08-10 10:48:34 +02:00
RichardandClaude Opus 4.8 b55512e734 Backup und Wiederherstellung einer Instanz
Gesichert wird alles, was sich nicht wiederherstellen laesst: Identity und Soul,
Konfigurationen, Arbeitsverzeichnisse, Chatverlaeufe und vor allem die Datenbank
mit dem Langzeitgedaechtnis. Protokolle bleiben standardmaessig aussen vor.

Zwei Punkte waren dabei nicht offensichtlich.

Die Datenbank darf nicht einfach kopiert werden. Mit WAL stehen die juengsten
Aenderungen in der Begleitdatei, nicht in der Hauptdatei — eine reine Kopie waere
veraltet oder in sich widersprueckhlich. VACUUM INTO erzeugt dagegen im laufenden
Betrieb eine geschlossene, konsistente Kopie; die WAL-Begleitdateien gehoeren dann
nicht mehr ins Archiv.

Zugangsdaten sind seit S7 mit DPAPI geschuetzt und damit an Benutzer und Rechner
gebunden. In einer Sicherung waeren sie genau dann unbrauchbar, wenn man sie
braucht — bei einem defekten Rechner. PassphraseProtector schluesselt sie deshalb
beim Sichern auf eine Passphrase um (PBKDF2 mit 210.000 Runden, AES-GCM) und beim
Wiederherstellen zurueck auf DPAPI des Zielrechners. Alternativ laesst sich eine
Sicherung ganz ohne Zugangsdaten erstellen; sie ist dann gefahrlos ablegbar, die
Wiederherstellung aber unvollstaendig.

Das Umschluesseln arbeitet auf dem JSON-Baum statt ueber die typisierten
Konfigurationsklassen. Beim Deserialisieren und erneuten Serialisieren gingen
unbekannte Felder verloren — eine Sicherung darf aber nichts wegwerfen, nur weil
eine Programmfassung ein Feld nicht kennt. Ein Test haelt das fest.

Weitere Eigenschaften: Manifest mit Pruefsummen je Datei, sodass ein veraendertes
Archiv auffaellt, bevor etwas ueberschrieben wird. Vorschau-Modus. Vorhandene
Dateien werden ohne ausdrueckliche Zustimmung nicht ueberschrieben. Eintraege, die
aus dem Zielverzeichnis herauszeigen, werden abgelehnt.

Der wichtigste Test ist der vollstaendige Rundlauf: Instanz aufbauen, sichern, in
ein leeres Verzeichnis wiederherstellen und pruefen, dass Persoenlichkeit,
Arbeitsstand, Gedaechtnis und nutzbare Zugangsdaten zurueck sind. Ein ungepruefte
Wiederherstellung ist kein Backup, sondern eine Vermutung.

385 Tests gruen (237 Core, 148 Tools).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 15:00:42 +02:00
RichardandClaude Opus 4.8 1157d28588 Konfig- und Zustandsdateien atomar schreiben
File.WriteAllText kuerzt die Zieldatei zuerst auf null und fuellt sie dann. Bricht
der Vorgang dazwischen ab, ist der alte Inhalt weg und der neue unvollstaendig.

Das ist im Betrieb bereits eingetreten: In der Instanz TradingTeam lag eine
TokenUsage.json.corrupt_..., die die Fehlerbehandlung beiseitegelegt hatte. Der
Verbrauch bis dahin war verloren.

AtomicFile schreibt in eine Nebendatei, erzwingt das Schreiben auf die Platte und
ersetzt dann. Umgestellt sind ChatHistory, ChatContext, alle Instanz- und
Agentenkonfigurationen, Identity und Soul, die App-Einstellungen sowie der
Stock-Index.

Zum Ersetzen wurde das Windows-Verhalten gemessen statt vermutet. Mit einem Leser,
der die Zieldatei geoeffnet haelt:

  Freigabe des Lesers      File.Move   File.Replace
  Read                     scheitert   scheitert
  ReadWrite                scheitert   scheitert
  ReadWrite | Delete       scheitert   funktioniert

File.Move verlangt die Zieldatei exklusiv und scheitert deshalb immer, sobald
jemand sie geoeffnet hat. Daher File.Replace — und ein Lesehelfer
AtomicFile.ReadAllText, der das Loeschen freigibt, damit unsere eigenen Leser
keinen Schreiber blockieren. Die Leser in InstanceDirectoryManager und beim Laden
der Chatverlaeufe nutzen ihn jetzt.

Zusaetzlich ein Schloss je Zieldatei: Zwei gleichzeitige Schreibvorgaenge auf
dieselbe Datei sind ohnehin ein Rennen, ohne Serialisierung scheitern sie aber
zusaetzlich mit "Zugriff verweigert". Fuer fremde Leser wie Virenscanner bleibt
eine Wiederholung mit Wartezeit.

366 Tests gruen (218 Core, 148 Tools).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 09:47:38 +02:00
RichardandClaude Opus 4.8 ef3e519f6c K5: Tagesbudget als harte Grenze
Es gab keine Obergrenze. Ein Agent in einer Schleife — etwa durch gegenseitige
send_message-Aufrufe — konnte unbeaufsichtigt Guthaben verbrennen; die
Credits-Anzeige war rein informativ.

Verbrauchsdaten in der Datenbank

Voraussetzung dafuer ist eine belastbare Erfassung. Bisher lag der Verbrauch in
TokenUsage.json: Bei JEDEM Agenten-Lauf wurde die gesamte Datei geladen, ergaenzt
und neu geschrieben, unter einem globalen Lock. Das waechst quadratisch und ist
der eigentliche Engpass bei vielen Agenten — unabhaengig davon, welche Datenbank
darunter liegt.

RunUsage liegt jetzt in einer eigenen Tabelle mit Ortsdatum, damit ein Tagesbudget
der Wahrnehmung des Benutzers folgt und die Abfrage ohne Zeitzonenrechnerei
auskommt. Betraege werden als Text abgelegt und als decimal gelesen: Ueber REAL zu
gehen wuerde bei Cent-Betraegen Rundungsfehler einsammeln, die sich ueber tausende
Laeufe summieren. Ein Test weist das mit 1000 Buchungen zu je 0,0001 USD nach.

BudgetGuard

Zwei Arten von Grenzen, weil sich Kosten nicht immer beziffern lassen: Liefert der
Anbieter fuer ein Modell keine Preise, greift die Kostengrenze nicht — die
Token-Grenze dagegen immer. Wer sich absichern will, setzt beide. Ist die Summe
wegen fehlender Preise unvollstaendig, steht das in der Begruendung; sonst wirkte
ein niedriger Verbrauch wie ein noch offener Spielraum.

Grenzen gibt es je Agent und je Instanz. Geprueft wird vor der ersten Anfrage,
damit ein erschoepftes Budget gar nichts mehr kostet. Neuer Endzustand
BudgetExceeded.

Nebenbei: Der Namespace Usage kollidierte mit der gleichnamigen Modellklasse fuer
Token-Angaben und heisst jetzt Accounting.

357 Tests gruen (209 Core, 148 Tools).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 18:16:21 +02:00
RichardandClaude Opus 4.8 4747835fa1 K1: Langzeitgedaechtnis fuer Agenten
Geplante Agenten begannen bei jedem Cron-Lauf bei null. Ein Agent, der alle 30
Minuten lief, wusste nichts von seinem letzten Durchgang — er rief dieselben
Quellen ab, zog dieselben Schluesse und konnte keine Entwicklung ueber Zeit
verfolgen. Das war zugleich die groesste Faehigkeitsluecke und eine dauerhafte
Token-Verschwendung.

Speicher-Fundament

SqliteStorage buendelt den Zugang zur Instanz-Datenbank und aktiviert WAL,
busy_timeout und Connection-Pooling. Vorher oeffnete jeder Aufruf eine Verbindung
ohne diese Einstellungen; bei mehreren gleichzeitig schreibenden Agenten gab das
"database is locked". Das sah nach einer Grenze von SQLite aus, war aber nur
fehlende Konfiguration. Zwei Tests decken das gezielt ab.

Gedaechtnis

Typisierte Tabelle statt JSON in einer Wert-Spalte — nur so laesst sich filtern,
sortieren und spaeter auswerten. Das Schema ist schlicht gehalten, damit eine
MySQL-Variante spaeter dieselbe Struktur mit wenigen Dialektunterschieden
bekommen kann.

Der wichtigste Teil ist der optionale Schluessel: Erneutes Merken darunter
aktualisiert den Eintrag, statt einen zweiten anzulegen. Ohne das wuechse das
Gedaechtnis eines halbstuendlich laufenden Agenten um 48 Eintraege pro Tag zur
selben Sache. Beobachtungen ohne Schluessel sammeln sich weiterhin an, wenn ein
Verlauf entstehen soll.

Der Abruf sortiert nach Wichtigkeit, dann Aktualitaet — wesentlich, weil das
Ergebnis begrenzt wird und bei einer Kappung das Wichtigste ueberleben muss.
Zusaetzlich greift eine Zeichenobergrenze, damit ein Abruf den Kontext nicht
sprengt.

Die Trennung privat/geteilt ist absichtlich dieselbe wie beim FileRW-Tool, damit
das Konzept fuer Agenten wiedererkennbar bleibt.

Beim Testen fiel auf, dass das Maskieren der LIKE-Platzhalter falsch war: Die
Zeichen wurden entfernt statt maskiert, wodurch eine Suche nach einem
Prozentzeichen zu einem leeren Muster und damit zu einem Treffer auf alles wurde.
Jetzt mit ESCAPE-Klausel.

338 Tests gruen (190 Core, 148 Tools).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 10:44:39 +02:00
RichardandClaude Opus 4.8 8604fa30c7 S7: Zugangsdaten in Konfigurationsdateien verschluesseln
OpenRouter-Schluessel, Datenbank-Verbindungszeichenfolgen samt Passwort,
Mail-Zugangsdaten und das Telegram-2FA-Passwort lagen im Klartext in
AgentSettings.json und InstanceSettings.json. Wer die Dateien lesen konnte — ein
Backup, eine Dateifreigabe, ein versehentlicher Commit — hatte alle Zugaenge.

SecretProtector nutzt DPAPI im Benutzerkontext: Die Werte lassen sich nur vom
selben Windows-Benutzer auf demselben Rechner lesen. Das schuetzt gegen
Weitergabe der Datei, nicht gegen einen Angreifer, der bereits als dieser
Benutzer laeuft — fuer einen lokal laufenden Dienst die angemessene Stufe.

Verschluesselte Werte tragen ein Praefix. Dadurch bleibt Klartext aus bestehenden
Konfigurationen lesbar und wird beim naechsten Speichern automatisch uebernommen;
vorhandene Installationen laufen ohne Zutun weiter.

Ein Wert, der sich nicht entschluesseln laesst — etwa nach Benutzer- oder
Rechnerwechsel — wird gemeldet statt stillschweigend als Klartext
durchgereicht. Sonst ginge ein unbrauchbarer Schluessel an die API und der
Fehler waere schwer zuzuordnen.

ConfigSecrets entscheidet anhand der Feldnamen, welche Werte betroffen sind. Das
ist noetig, weil die Tool-Konfiguration ein freies Woerterbuch ist. Beim Speichern
werden die Werte nur fuer den Schreibvorgang verschluesselt und danach wieder
entschluesselt, damit die laufende Instanz weiterarbeiten kann.

309 Tests gruen (161 Core, 148 Tools).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 10:21:08 +02:00
RichardandClaude Opus 4.8 93f47aca3a Modellpreise live vom Anbieter beziehen
Zweiter Teil von B4. Die Preise standen fest im Code — eine Tabelle mit einer
Handvoll Modelle, die bereits veraltet war. Ausgerechnet das Standardmodell der
Agenten fehlte darin, und CalculateCost gab fuer unbekannte Modelle
stillschweigend 0 zurueck. Die Kostenanzeige war damit nicht bloss ungenau,
sondern blind: Sie meldete 0 Euro, waehrend echte Kosten anfielen.

ModelInfo traegt jetzt die Preisangaben aus dem /models-Endpunkt. OpenRouter
liefert sie als Text und pro einzelnem Token; die Umrechnung auf eine Million
Token laeuft ueber InvariantCulture, sonst wuerde eine deutsche Systemsprache
0.000003 als drei lesen.

Halbe Angaben werden verworfen: Ein Modell, bei dem nur der Eingabepreis
vorliegt, ergaebe eine plausibel aussehende, aber falsche Summe.

ModelPricingCatalog haelt die Preise und liefert eine Kostenschaetzung, die
ausweist, ob sie belastbar ist. Der Statusdienst laedt den Katalog beim Start und
markiert Laeufe ohne Preisangabe sichtbar — in der Zeile mit einem Warnzeichen,
im Tooltip mit den betroffenen Modellnamen und dem Hinweis, dass die Summe
unvollstaendig ist.

284 Tests gruen (136 Core, 148 Tools).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 10:09:39 +02:00
RichardandClaude Opus 4.8 cbb8ac22bc SSRF-Schutz, Pfadpruefung und Retry-Logik
S5 — Die Domain-Whitelist wurde nur auf die Ausgangs-URL angewendet, HttpClient
folgte Weiterleitungen aber selbst. Eine erlaubte Domain konnte damit auf
beliebige interne Adressen weiterleiten: Router, NAS, der Git-Server im LAN,
Cloud-Metadatendienste. Der Agent haette deren Inhalt zurueckgeliefert.

UrlGuard prueft Schema, private und lokale Netzbereiche sowie die Whitelist.
AllowAutoRedirect ist abgeschaltet; Weiterleitungen werden einzeln aufgeloest und
JEDER Zwischenschritt erneut geprueft, begrenzt auf fuenf Spruenge.

Nebenbei behoben: Die alte www-Behandlung ersetzte die Zeichenfolge ueber den
ganzen Hostnamen, aus mywww.example.com wurde myexample.com. Und die
Subdomain-Pruefung achtet jetzt auf den Punkt, sodass example.com.attacker.net
nicht mehr als Treffer fuer example.com durchgeht.

S6 — Die Pfadpruefung in FileRW verglich nur Zeichenketten-Praefixe. Ohne
abschliessenden Verzeichnistrenner erlaubte ein Root wie Agent-X\Workspace damit
auch Zugriffe auf Agent-X\Workspace-Backup. WorkspacePath vergleicht jetzt auf
Verzeichnisgrenzen und lehnt zusaetzlich absolute Pfade, UNC-Freigaben und
Alternate Data Streams ab.

Das Dateisystem wird in den Tests bewusst nicht abstrahiert — sie sollen die
echte Windows-Pfadsemantik pruefen. Eine Abstraktion wuerde genau die
Fehlerklasse verstecken, um die es geht.

B12 — Der Client warf bei jedem Nicht-2xx sofort; ein einzelnes HTTP 429 beendete
damit einen kompletten geplanten Lauf, obwohl Rate-Limits und kurze 5xx bei
OpenRouter Normalbetrieb sind. RetryPolicy wiederholt 408/425/429/5xx mit
exponentiellem Backoff und Streuung, respektiert ein Retry-After des Servers und
deckelt die Wartezeit. Dauerhafte Fehler wie 401 werden nicht wiederholt.

Die Wartefunktion ist injizierbar, damit die Tests nicht wirklich warten.

264 Tests gruen (116 Core, 148 Tools).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 00:24:03 +02:00
RichardandClaude Opus 4.8 e5067cae70 Sicherheitsluecken S1, S2 und S3 schliessen
Neues Testprojekt tests/ClawdDotNet.Tools.Tests. Die Angriffsfaelle aus der
Bestandsaufnahme bleiben darin dauerhaft als Testfaelle dokumentiert — zusammen
mit Gegenproben, damit die Fixes nicht zu streng werden und legitime Nutzung
blockieren.

S3 — DirectAPI gab die abgerufene URL als Quelle an das Modell zurueck, samt
API-Schluessel im Query-String. Der Schluessel landete damit im
Konversationskontext, wurde bei jedem Folgeschritt erneut gesendet, in
ChatContext.json geschrieben und in die Logs uebernommen. UrlSanitizer maskiert
sensible Query-Parameter; auch die Fehlermeldungen sind betroffen und werden
bereinigt.

S2 — Die Kanal-/Video-Angabe wurde ungeprueft in eine Argument-Zeichenkette fuer
yt-dlp interpoliert. UseShellExecute=false verhindert Shell-Metazeichen, nicht
aber Options-Injection: yt-dlp kennt die Option --exec, die beliebige Befehle
ausfuehrt. Kritisch, weil der Agent untrusted Inhalte verarbeitet — eine
Prompt-Injection darin konnte ihn dazu bringen, genau so einen Wert zu setzen.

YouTubeUrl validiert Handles und URLs gegen die zulaessigen YouTube-Hosts und
lehnt alles ab, was mit einem Bindestrich beginnt. Die Argumente gehen jetzt
einzeln ueber ProcessStartInfo.ArgumentList, die Adresse steht hinter dem
Optionsende-Trenner. Der ffmpeg-Aufruf wurde ebenso umgestellt.

S1 — Die Tabellen-Whitelist suchte den erlaubten Namen als Teilzeichenkette
irgendwo im Statement, auch in Kommentaren. Bei einer Freigabe fuer prices
genuegte deshalb ein DELETE auf users mit einem Kommentar, der prices enthielt,
um eine beliebige Tabelle zu loeschen. Umgekehrt galten harmlose Abfragen, die
ein Schluesselwort nur als Wert enthielten, faelschlich als Schreibzugriff.

SqlGuard entfernt zuerst Kommentare und String-Literale, lehnt mehrere Statements
ab, bestimmt die Operation am ersten Schluesselwort und extrahiert Tabellennamen
gezielt hinter FROM/JOIN/INTO/UPDATE/TABLE — inklusive kommagetrennter Listen mit
Aliassen. JEDE referenzierte Tabelle muss freigegeben sein, nicht irgendeine.
Ohne Whitelist wird nichts durchgelassen. Die MongoDB-Pruefung vergleicht den
Collection-Namen jetzt exakt statt per Teilzeichenkette.

178 Tests gruen (91 Core, 87 Tools).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 18:49:17 +02:00