Fuer den Betrieb fehlte bisher eine Rolle: "user" meldet zwar Daten, bleibt in der
Buero-Visualisierung aber unsichtbar, weil sie nie einen Schreibtisch bekommt. Der
Ping-Check aus check_fds.sh (Weg 3 im README) lief deshalb als "user" und tauchte im
Buero nicht auf.
"ops" fuellt die Luecke: die Schreibtischvergabe in AgentRepository::register() greift
automatisch, sobald die Rolle erlaubt ist - nur "user" bleibt ohne Platz.
Der CHECK-Constraint auf agents.role braucht dafuer eine eigene Migration. Ein Edit von
001_initial.sql wuerde nur frische Datenbanken erreichen: bestehende Installationen haben
die Migration laut schema_migrations laengst angewandt und wuerden bei role='ops'
weiterhin abbrechen. SQLite kann CHECK nicht per ALTER TABLE aendern, 004 baut die Tabelle
deshalb neu (Verfahren aus der SQLite-Doku, foreign_keys kurz aus, weil
tickets.assignee_agent_id auf agents zeigt).
Im Frontend bekommt die Rolle Label "OPs", eine Akzentfarbe und ein Zahnrad als
Schreibtisch-Deko - ohne Eintrag in der ROLES-Map rendert der Avatar grau und ohne Label.
Neu ist ausserdem register-ops-agent.sh neben der docker-compose.yml: es liest Admin-Key
und Basis-URL aus der .env, registriert den lokalen OPs-Agenten und schreibt den nur
einmalig ausgegebenen API-Key mit chmod 600 nach monitor-api.conf - also genau die beiden
Schritte aus Weg 3, die hosts.conf und credentials.conf nicht abdecken. Eine vorhandene
monitor-api.conf ueberschreibt es nicht, und ein zweiter Agent gleichen Namens (der einen
weiteren der acht Schreibtische belegen wuerde) wird vorher abgefangen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CodebergSyncService berechnet am Ende jedes Sync-Laufs (periodisch und
webhook-getriggert, auch bei PR-Aenderungen) die Summe der offenen Pull
Requests aller konfigurierten Projekte und speichert sie als KPI
"open_prs" im bestehenden generischen KPI-Repository - sie erscheint
damit automatisch im KPI-Monitor der Wand und im KPI-Modal.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Nach dem Anlegen/Bearbeiten eines Projekts im Konfig-Dialog prueft ein neuer Endpunkt
(POST /api/v1/repo-configs/{id}/check), ob fds2610-bot dort Schreibrechte hat und ein
aktiver Webhook auf diese monitor-Instanz zeigt. Das Ergebnis erscheint als gruener Haken
bzw. rotes Kreuz rechts neben den Bearbeiten-/Loeschen-Symbolen, mit Tooltip zum geprueften
Service bzw. zur Fehlerursache.
Manchmal werden bei Codeberg geloeschte oder anderweitig verschwundene Issues
vom periodischen Sync nicht bereinigt (er upsertet nur, ruehrt fehlende Issues
nie an). Neuer Knopf neben der "Kanban-Board"-Ueberschrift stoesst dafuer einen
synchronen Voll-Resync an (POST /api/v1/tickets/resync, X-Admin-Key): jedes
konfigurierte Projekt wird neu gelesen, Tickets zu inzwischen verschwundenen
Issues werden je Projekt entfernt (auch wenn sie manuell auf eine nicht vom
Sync verwaltete Spalte wie "fertig" bewegt wurden), Tickets zu weiterhin
existierenden Issues bleiben unangetastet.
Der PO geht dabei sichtbar ans Board und steht dort, bis die Anfrage
abgeschlossen ist - anders als beim automatischen Board-Besuch (feste
Animationsdauer je Stage) macht das die tatsaechliche Sync-Dauer sichtbar.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Der Agent (fds2610/Agent#9) setzt "help wanted" auf Issues, waehrend er auf
die Erfuellung einer externen Voraussetzung wartet. Solche Issues sollen im
Monitor-Board in der Spalte "QA" auftauchen statt im Backlog zu verschwinden.
"qa" wird dafuer wie "backlog"/"bereit"/"in_arbeit"/"testing"/"done" zur
sync-verwalteten Spalte; nur "fertig" bleibt weiterhin fuer ein zukuenftiges
Label reserviert und rein manuell.
Bislang hat RepoConfigController::delete() nur die Projektkonfiguration
geloescht - die Tickets des Projekts blieben (nur ohne Projektfarbe,
Issue #34) dauerhaft im Kanban sichtbar. Jetzt loescht der Endpunkt
zusaetzlich alle Tickets, deren repo-Feld auf das entfernte Projekt
zeigt.
Die im Issue beschriebene Event-Anzeige (neuestes Event oben, aeltere
schieben nach, Ueberschuss wird abgeschnitten) ist bereits seit Phase 4
umgesetzt (buildEventLines()/MAX_EVENT_LINES, F5) und braucht keine
Aenderung.
Neu ist die vierte, immer sichtbare KPI-Kachel ganz rechts im
KPI-Monitor: "Hooks: n" zaehlt die seit dem letzten Start des
api-Containers empfangenen Webhooks. Der Zaehler ist dateibasiert
(Metrics\HookCounter), weil der eingebaute PHP-Server jede Anfrage als
eigene Skriptausfuehrung behandelt; docker/entrypoint.sh setzt die Datei
beim Start des api-Containers (nicht des sync-Containers) zurueck. Neuer
oeffentlicher Endpunkt GET /api/v1/webhooks/stats liefert den Stand,
das Frontend haengt ihn als eigene Kachel an (buildHooksKpi()) und
reserviert ihr dafuer eine feste Kachel statt sie der MAX_KPI_TILES-
Kappung der uebrigen KPIs zu unterwerfen. Der Wert fliesst zusaetzlich
in den SSE-Fingerabdruck (ChangeFeed) ein.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wenn ein Projekt im Projekte-Modal entfernt oder umbenannt wurde, hat
CodebergSyncService dessen Tickets nicht mehr angefasst - sie blieben
also dauerhaft in der alten Projektfarbe eingefaerbt, obwohl es dafuer
gar kein Projekt mehr gab (Issue #34). sync() setzt die Farbe solcher
Tickets nun nach jedem Lauf auf NULL zurueck, wodurch das Kanban die
Standardfarbe statt der veralteten Projektfarbe zeigt.
Schließt #34
Schreibende Aufrufe aus dem Frontend (POST/PATCH/DELETE auf /repo-configs und
/agents) schicken X-Admin-Key und Content-Type: application/json. Beides macht
aus dem fetch() einen preflighted request: Der Browser fragt zuerst per OPTIONS
nach. Fuer OPTIONS war aber keine Route registriert, der Router warf daher
MethodNotAllowedException (405), und ausserdem fehlte
Access-Control-Allow-Headers fuer den X-Admin-Key. Der Browser sendete den
eigentlichen POST damit nie und das Modal meldete nur "Netzwerkfehler beim
Abrufen der Ressource" - Lesen funktionierte, weil GET ohne Custom-Header ein
simple request ohne Preflight ist.
Application::handle() beantwortet OPTIONS jetzt vor dem Routing mit 204, und
Response::send() liefert Allow-Methods, Allow-Headers und Max-Age mit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Konfig-Dialog liess sich in der Praxis kaum oeffnen (Fehlerbericht in
Issue #27). Drei Ursachen, alle reproduziert:
- Ein Klick auf den PO selbst tat gar nichts: der Klick-Handler hing nur am
Schreibtisch, die Figur liegt aber in der Avatar-Schicht darueber. Genau sie
meint das Ticket ("bei Klick auf den PO"). Der Avatar ist jetzt klickbar,
auch waehrend er am Board steht.
- Klicks auf den Platz trafen nur, wenn sie zufaellig auf einem gefuellten
Moebelstueck landeten - eine <g> faengt in ihren Luecken keine Klicks. Der
Platz hat jetzt eine eigene Trefferflaeche, wie die Board-Spalten.
- Dass hinter dem PO ueberhaupt etwas steckt, war nicht zu sehen. Ein kleines
Zahnrad auf seinem Tisch (mit Tooltip) zeigt es an.
Ausserdem oeffnet der Dialog jetzt auch bei nicht erreichbarer API - er meldet
dann selbst, dass er die Projekte nicht laden konnte, statt tot zu wirken.
Und genau dieser Fall trat leicht ein: der eingebaute PHP-Server beantwortet
Anfragen nacheinander, eine offene SSE-Verbindung blockiert damit alles andere
(gemessen 16s fuer ein GET /api/v1/tickets, ab dem zweiten offenen Monitor
dauerhaft). Das Frontend faellt dann auf Demo-Daten zurueck. Mit
PHP_CLI_SERVER_WORKERS=4 sind es 0,006s; PRAGMA busy_timeout deckt die
Schreibzugriffe ab, die sich jetzt begegnen koennen.
ApplicationTest::testFullFlowCreateUpdateAndDeleteRepoConfigWithAdminKey war
seit dem Seed in 003_repo_configs.sql rot (legte fds2610/monitor ein zweites
Mal an) - der Test nutzt jetzt ein nicht geseedetes Repo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Klick auf den PO oeffnet ein Modal, in dem sich die Projekte (Owner/Repo +
Farbe), aus denen der Codeberg-Sync liest, hinzufuegen, bearbeiten und
loeschen lassen - Icon-Schaltflaechen mit Tooltip statt Textbuttons. Die
Projektliste war bisher eine Umgebungsvariable (CODEBERG_REPOS, Issue #26)
und wandert dafuer in eine neue Tabelle repo_configs (Migration
003_repo_configs.sql, geseedet mit den bisherigen drei Default-Projekten);
CodebergSyncService liest sie ueber RepoConfigRepository bei jedem Lauf
frisch ein, damit eine Aenderung im laufenden --watch-Prozess ohne Neustart
wirkt. Neue Endpunkte GET/POST/PATCH/DELETE /api/v1/repo-configs, Schreiben
hinter X-Admin-Key wie die Agenten-Registrierung; das Frontend fragt den Key
bei der ersten Aenderung per Prompt ab und merkt ihn sich im lokalen
Speicher. office-modal.js bekommt dafuer einen Oeffnen-Token
(isCurrentModal()), damit ein verspaetet aufloesender Request nach dem
Oeffnen eines anderen Modals nicht dessen Inhalt ueberschreibt.
Schliesst #27
Der Codeberg-Sync liest nun Issues aus mehreren konfigurierbaren Projekten
(Config::codebergRepos(), Default: fds2610/monitor gelb, fds2610/Steuerkram
gruen, fds2610/nc-gantt blau) statt nur aus einem einzigen Repo. Damit
external_ref (Issue-Nummer) projektuebergreifend nicht kollidiert, ist es
jetzt zusammen mit dem neuen Ticket-Feld "repo" eindeutig; Migration
002_multi_repo_tickets.sql ergaenzt die Spalte und den Index. Da Migrationen
dadurch nicht mehr rein additiv sind, trackt Database::migrate() jetzt
angewandte Dateien in schema_migrations, damit ALTER TABLE nicht bei jedem
Prozessstart erneut laeuft. Das Frontend baut Codeberg-Links jetzt aus dem
repo-Feld jedes Tickets statt aus einer global fixen owner/repo-Angabe.
Schliesst #26
Auf der Produktionsmaschine traten vereinzelt Timeouts nach codeberg.org auf
("Operation timed out after 15003 milliseconds with 0 bytes received"), waehrend
die Verbindung sonst in ~0,1s antwortet.
Bisher fiel der Dauerlaeufer nach so einem Fehlschlag in den regulaeren Takt
zurueck. Weil SyncTrigger::consume() die Webhook-Anforderung schon vor dem Lauf
quittiert, war eine per Webhook angeforderte Aktualisierung damit verloren und
kam erst bis zu MONITOR_SYNC_INTERVAL Sekunden spaeter - genau die Verzoegerung,
die der Webhook abschaffen sollte.
Neu wird der naechste Versuch nach einem Fehlschlag vorgezogen: 5s, 10s, 20s, …
gedeckelt auf das regulaere Intervall (Sync\SyncSchedule). Ein kurzer Aussetzer
ist damit binnen Sekunden ueberstanden, eine laengere Codeberg-Stoerung laeuft
trotzdem nicht in einen Dauerbeschuss. Nach einem erfolgreichen Lauf gilt sofort
wieder das regulaere Intervall.
Die Wartezeit-Berechnung liegt in einer eigenen Klasse, weil die Schleife in
bin/sync-codeberg.php selbst nicht testbar ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aenderungen an Codeberg-Issues landeten bisher erst beim naechsten Lauf des
sync-Services auf dem Board (MONITOR_SYNC_INTERVAL, Default 600s). Neu meldet
Codeberg jede Aenderung aktiv per Webhook, der Weg aufs Board dauert damit
wenige Sekunden (Webhook -> Sync -> SSE-Push ans Frontend).
POST /api/v1/webhooks/codeberg wertet den Payload bewusst nicht aus, sondern
fordert nur einen regulaeren Sync an - CodebergSyncService bleibt die einzige
Stelle, die Issues auf Tickets abbildet, und ein verpasster Webhook ist
folgenlos, weil der periodische Lauf denselben Stand ohnehin nachholt.
Der Sync passiert nicht im Request: Forgejo bricht die Zustellung nach wenigen
Sekunden ab (DELIVER_TIMEOUT, Default 5s), ein Sync macht aber mehrere Requests
nach codeberg.org. Der Endpunkt setzt daher nur eine Trigger-Datei im
gemeinsamen ./api-Volume (Sync\SyncTrigger) und antwortet sofort mit 202; der
sync-Service laeuft jetzt im Watch-Modus und greift sie im Sekundentakt auf.
Authentifiziert wird per HMAC-SHA256 ueber den rohen Request-Body
(X-Forgejo-Signature bzw. X-Gitea-Signature) - Codeberg kennt unsere API-Keys
nicht. Ohne gesetztes CODEBERG_WEBHOOK_SECRET weist der Endpunkt grundsaetzlich
mit 401 ab, damit ein vergessenes Secret keinen offenen Sync-Ausloeser im
Internet hinterlaesst. Request traegt dafuer neu den unveraenderten rawBody.
Anders als der sync-Service braucht der api-Service damit eine eingehende
Freigabe in Proxy und Firewall (POST /api/v1/webhooks/codeberg).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Client verglich last_seen_at bislang selbst gegen Date.now() - je nach
Client-Zeitzone/-Uhr konnte das falsche Ergebnisse liefern und liess Agenten
faelschlich als abwesend erscheinen (Issue #20, Folge von #17). Die API
liefert die Anwesenheit jetzt direkt als is_present-Feld mit, berechnet
serverseitig via gmdate()/time() (AgentRepository::isPresent()). Das Frontend
liest das Feld nur noch aus, ohne eigene Zeit-Arithmetik.
Bisher landeten alle offenen Issues unabhaengig von ihren Labels in
Backlog. Jetzt bestimmt das Label die Spalte: wip -> in_arbeit,
testing -> testing, automate -> bereit, sonst backlog. Geschlossene
Issues bleiben in Done. Die "manuell verschoben"-Sperre im
TicketRepository schuetzt jetzt nur noch qa/fertig (zukuenftige
Labels) statt aller Nicht-Backlog/Done-Spalten, damit der Sync
Tickets weiterhin zwischen den automatisierten Spalten verschieben
kann, wenn sich Labels aendern.
Schliesst #14
Setzt Visualization.md Abschnitt 9, Phasen 3-5 vollstaendig um:
Phase 3 (Kanban-Board live): Board zeigt echte Ticket-Daten aus GET /api/v1/tickets
(office-api.js), Klick auf eine Spalte oeffnet ein Modal mit Titel/Beschreibung/Typ und
einem Link auf die Codeberg-Quelle bei per Sync angelegten Tickets (F4).
Phase 4 (Monitor-Wand live): Event-, System- und KPI-Monitor zeigen echte Daten,
Klick-Modals fuer alle drei. Neuer SSE-Endpoint GET /api/v1/stream (F13) meldet per
ChangeFeed-Fingerabdruck, welche Ressourcen sich geaendert haben; office-realtime.js nutzt
EventSource mit Polling-Fallback. KPI-Monitor zeichnet eine eigene SVG-Sparkline (F10, kein
Chart-Lib) sobald KPIs gemeldet wurden, sonst weiterhin der Phase-2-Platzhalter (F7 bleibt
inhaltlich offen). API-Antworten tragen jetzt CORS-Header, da Frontend (:8090) und API
(:8091) im Docker-Compose-Setup unterschiedliche Origins sind.
Phase 5 (Agenten & Animation): resolveAgentVisualState bildet gemeldete Agenten-Zustaende
auf Ort/Pose ab; Agenten-Avatare sind eine per CSS transition animierte, von Board/
Schreibtisch-Moebeln entkoppelte Schicht, die zwischen Schreibtisch, Board, Monitor-Wand und
einer neuen Kaffee-Ecke laufen. Idle-Varianten (Zeitung/Kaffee/Schwaetzchen) werden rein
kosmetisch client-seitig zufaellig ausgeloest (F8), eine AnimationQueue serialisiert
Update-Animationen bei vielen gleichzeitigen Ereignissen (F9).
29 Node-Tests (100% Line-Coverage office-scene.js) und neue PHPUnit-Tests fuer
Response::stream(), ChangeFeed, StreamController und die neuen Repository::fingerprint()-
Methoden. PHP/Docker-Ausfuehrung konnte in dieser Umgebung nicht verifiziert werden (kein
PHP verfuegbar) - vor dem Merge bitte `docker compose run --rm api composer test` pruefen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1. AgentRepository::updateActivity() schrieb current_ticket_id ungeprueft. Eine
unbekannte Ticket-ID lief in die FOREIGN-KEY-Constraint und kam als
ungefangene PDOException samt Stacktrace und Dateipfaden beim Client an.
Jetzt wird die Existenz vorab geprueft (422). Zusaetzlich faengt
Application::handle() unerwartete Throwables ab und antwortet mit einem
generischen 500 - Details gehen nur noch ins Log.
2. Die Normalisierung der Codeberg-Label-Farbe ("#" davor) lag im
CurlCodebergClient statt im CodebergSyncService. Damit haetten andere
Client-Implementierungen unterschiedliche Farbformate ins Ticket geschrieben.
Verschoben in die Domaenenschicht, leere Farben ergeben jetzt null.
3. Config::get() griff selbst auf getenv() zurueck, wodurch ein in der Umgebung
gesetzter CODEBERG_TOKEN in eine explizit gebaute Config durchschlug - im
Testlauf landete der Token im Klartext in der PHPUnit-Ausgabe. Die Umgebung
wird jetzt ausschliesslich in fromEnvironment() eingesammelt.
Regressionstests fuer alle drei Punkte ergaenzt: 104 Tests, 181 Assertions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Setzt Phase 1 aus Visualization.md um: Vanilla-PHP-REST-API mit SQLite-Persistenz,
statischen API-Keys pro Agent, Codeberg-Sync fuer Backlog/Done, PHPUnit-Tests und
vollstaendiger API.md-Dokumentation - Vorgaben aus Detailantworten.md (F1-F16)
umgesetzt. Entwicklung/Build/Test laufen komplett in Docker-Compose-Containern
(PHP 8.2 + SQLite + Composer + PHPUnit/pcov), analog zum NC-Gantt-Projekt ist auf
dem Host kein PHP/Composer noetig.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>