149 Commits

Author SHA1 Message Date
chahinebrini
6fa94116a4 fix(hub): offene Asks sichtbar machen + Erinnerungsflut stoppen (TSK-0406)
Am 09.08. stellte sich heraus, dass fuenf Asks auf pending standen, zwei
seit einer Woche. Vier Agenten waren blockiert — darunter einer, der einen
fertigen Windows-Build fuer einen externen Tester nicht ausliefern durfte.
Der Architekt hat die ganze Zeit das Board gepollt und nichts davon gesehen.
Aufgefallen ist es nur, weil ein Agent den Stau selbst benannt hat.

TEIL 1 — SICHTBARKEIT (Umsetzung: codex)
Asks sind ein eigener, BLOCKIERENDER Kanal, den keine der Oberflaechen
erwaehnte, die ein Architekt regelmaessig ansieht:
- core/status.ts nennt jetzt Anzahl, IDs und Alter des aeltesten Asks.
  Das Alter ist der eigentliche Alarm: "einer offen" ist normal,
  "seit sechs Tagen offen" ist ein Notfall.
- /health liefert pendingAsks + oldestAskAgeSec — der billigste Statuscheck
  des Architekten; was dort nicht steht, existiert fuer ihn nicht.
- Der Watchdog kannte Asks gar nicht. Er meldete brav "silent for 16m",
  also das Symptom, waehrend die Ursache unsichtbar blieb. Jetzt alarmiert
  er bei ueberfaelligen Asks mit steigender Dringlichkeit, und die
  irrefuehrende "silent"-Meldung wird bei einem offenen Ask ersetzt durch
  "wartet seit Xh auf ASK-NNNN — bitte antworten, nicht neu starten".
  Genau diese Verwechslung fuehrte dazu, dass wartende Agenten fuer tot
  gehalten und ihre Sessions neu gestartet wurden.
- watch --await-ask weckt den Architekten bei neuen Asks (mit --new-only).
- Das Board zeigt offene Asks mit Alter und wartendem Agenten.

TEIL 2 — DIE FLUT (Umsetzung: claude)
Der Watchdog erinnerte einen nicht beanspruchten Task unbegrenzt im
Basisintervall weiter — auch an Agenten, von denen er WUSSTE, dass sie
nicht im work-Loop sind. Ergebnis: ueber 1500 ungelesene Nachrichten fuer
einen einzigen Agenten, dessen Loop daran nicht mehr anlief. Ein Alarm,
der den Empfaenger handlungsunfaehig macht, ist schlimmer als kein Alarm.
- Ist der Assignee nachweislich aus dem Loop ausgestiegen (nicht: nie
  gesehen), wird KEINE Erinnerung mehr in sein Postfach geschrieben.
  Eine Nachricht ist ein dauerhaftes Artefakt; in ein Postfach zu
  schreiben, das niemand liest, baut nur den Rueckstau auf, der spaeter
  seinen eigenen Loop blockiert. Stattdessen genau EIN Hinweis an den
  Architekten — ein Session-Neustart ist eine menschliche Entscheidung.
- Das ephemere SSE-Reemit bleibt in beiden Zweigen: es kostet nichts und
  erreicht womoeglich einen Agenten, der gerade neu verbindet.
- Fuer erreichbare Agenten waechst der Abstand exponentiell (Basis x 2^n,
  gedeckelt bei 8x) statt konstant zu bleiben.
- Der Zaehler wird zurueckgesetzt, sobald der Task beansprucht wird, damit
  ein spaeteres Reopen nicht in einem halb stummgeschalteten Zustand
  startet.

Der bestehende Test "does not spam" pruefte, dass nach erneutem Ablauf der
BASIS-Schwelle eine zweite Erinnerung kommt — also genau das Verhalten,
das die Flut erzeugt hat. Er ist auf den Backoff angepasst; seine Absicht
(Anti-Spam) gilt jetzt ueber die Lebensdauer eines Tasks statt nur ueber
ein Cooldown-Fenster.

316 Tests gruen, tsc --noEmit sauber.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 12:29:09 +02:00
chahinebrini
df698c9dff feat(agenthub): Rollenwechsel, agent-setup + Push-Channel
roleService haelt roles.*.preferredAgent (Routing-Autoritaet fuer asks,
Watchdog, start) und agents.*.role (Health/Board/MCP-Discovery) konsistent —
bisher konnte nur eine Seite gesetzt werden und der Roster lief auseinander.
Dazu agent-setup-Kommando, Push-Channel im MCP und Selbstheilung im
remoteClient.

Build clean, 304 Tests in 55 Dateien gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113SC5FkaMxcRzXHDBsHmdJ
2026-08-02 21:38:47 +02:00
chahinebrini
908a043ac5 fix(work): bereits gehaltene Task wird genannt statt verschwiegen
Vom CEO gefunden: TSK-0293 stand auf in_progress bei codex — und codex tat
nichts. Ursache: `findAddressedOpenTask` sucht ausschliesslich Tasks mit
Status `open`. Haelt ein Agent bereits eine in_progress-Task (typisch nach
einem Session-Neustart), greift der Busy-Guard und der Loop liefert NICHTS
zurueck. Der Agent wartet dann auf neue Arbeit, die nie kommt, waehrend seine
eigene Task unbearbeitet liegt — und von aussen sieht es aus, als sei er tot.

`resolvePending` liefert jetzt `heldTask`, und alle drei Wege sagen es dem
Agenten deutlich:
  - `agenthub start <agent>`: "DU HAELTST BEREITS <id>" ganz oben
  - `agenthub work` (CLI): nennt die Task und beendet sich, statt zu warten
  - `agenthub_work` (MCP): gibt Task samt Body zurueck statt zu blockieren

Tests: 295 → 297.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 21:45:15 +02:00
chahinebrini
8a1728cbce fix(server): Wurzel-URL leitet aufs Board statt 404
http://<host>:3377/ antwortete mit 404 — das Board liegt auf /board. Wer
prueft, ob der Hub laeuft, oeffnet aber genau die Wurzel-URL und haelt ihn
dann faelschlich fuer tot. Vom CEO gemeldet, mir selbst heute einmal passiert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 21:40:59 +02:00
chahinebrini
dac8afb3c1 fix(cli): Selbstheilung bevorzugt Loopback — sie heilte sich bisher in die instabilere Adresse
Vom CEO an codex' Log entdeckt: der CLI-Worker scheiterte mit
"AgentHub server at http://192.168.178.52:3377 is not reachable" — obwohl die
MCP-Bridge laengst auf 127.0.0.1 zeigte. Grund: die CLI nimmt die
PROJEKT-Config, nicht die MCP-Umgebungsvariable.

Und die Projekt-Config wurde immer wieder zurueckgeschrieben: antwortet die
konfigurierte Adresse kurz nicht — bei JEDEM Hub-Neustart der Fall —, sucht
resolveContext per mDNS, findet die LAN-IP und persistiert sie. Ein zuvor
gesetztes Loopback ging dabei jedes Mal verloren. Die LAN-IP wiederum bricht
beim naechsten Neustart oder Netzwechsel weg: das System heilte sich
zuverlaessig in die instabilere Adresse hinein.

`preferLoopback()` prueft jetzt, ob derselbe Hub auch auf 127.0.0.1 antwortet,
und persistiert dann Loopback. Fuer Agenten auf derselben Maschine ist das
strikt robuster — es kann weder durch mDNS-Neuankuendigung noch durch einen
Netzwechsel ungueltig werden. Projekt-Config zusaetzlich zurueckgesetzt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 21:36:42 +02:00
chahinebrini
6e614b1f4d fix(briefing): Hintergrund-Worker ohne Timeout als ERSTE Aktion — CEO-Vorschlag
Der CEO hat den Kern benannt: beide Agenten sollen beim Start einen
Hintergrund-Worker fahren, so wie der Architekt seine watch-Prozesse.

Messung dazu: der CLI-Worker `agenthub work` hat OHNE --timeout gar keine
Wartegrenze — er blockiert unbegrenzt und endet erst, wenn Arbeit kommt. Das
MCP-Tool braucht dagegen zwingend einen Timeout (MCP-Client-Limit). Genau
darin unterschieden sich die beiden Agenten die ganze Zeit: kimi fuhr den
CLI-Worker im Hintergrund (kein Timeout, ueberlebt Turn-Grenzen), codex rief
das MCP-Tool im Turn auf (50s, stirbt mit dem Turn).

Das Briefing macht den Hintergrund-Worker jetzt zur ERSTEN Aktion und sagt
ausdruecklich: ohne --timeout, als Hintergrund-Task der eigenen Umgebung, so
dass dessen Ende den Agenten weckt. Das Tool ist nur noch Notnagel fuer Hosts
ohne Hintergrund-Tasks — und dann ohne timeoutSec, damit der Roster-Wert gilt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 21:29:48 +02:00
chahinebrini
178883cd2e fix(briefing): Work-Loop als Hintergrundprozess — und kein hartkodiertes timeoutSec mehr
Zwei selbstverschuldete Fehler, vom CEO an den echten Agenten-Logs entdeckt:

1) Das Briefing schrieb `timeoutSec=50` vor. codex uebergab den Wert brav —
   und ein explizites Argument sticht die Roster-Einstellung. Der gerade
   gebaute per-Agent-Timeout (codex 240s) war damit wirkungslos. Jetzt steht
   dort ausdruecklich: OHNE timeoutSec aufrufen, der Roster entscheidet.

2) Der eigentliche Unterschied zwischen den Agenten stand die ganze Zeit im
   kimi-Log: kimi startet den Loop als HINTERGRUNDPROZESS
   ("bash task started in background"), codex als Tool-Call mitten im Turn.
   Ein Hintergrundprozess ueberlebt das Turn-Ende, ein Tool-Call nicht —
   deshalb war codex nach jedem leeren Aufruf still und kimi nicht. Das
   Briefing verlangt jetzt fuer alle den Hintergrundprozess
   (`agenthub work --agent <name>`), der Tool-Call ist nur noch Notnagel.

Damit ist der Unterschied strukturell adressiert statt agentenspezifisch
umgangen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 21:26:05 +02:00
chahinebrini
55606eddc8 diag(mcp): Client-Capabilities beim Binden festhalten
Der Push-Test mit den echten CLIs war NEGATIV: codex meldete "durch
agenthub_work erfahren", kimi ausdruecklich "kein MCP-Push". Der Kanal
funktioniert (mit eigenem Testclient verifiziert), die Hosts reichen ihn
aber offenbar nicht ans Modell durch.

Naheliegender Verdacht, statt zu spekulieren gemessen: `sendLoggingMessage`
nuetzt nichts, wenn der CLIENT `logging` nicht deklariert — dann verwirft
seine SDK-Seite die Notification, bevor das Modell sie je sieht. Mein
Testclient deklarierte sie ausdruecklich und empfing deshalb.

Die Bridge schreibt die deklarierten Client-Capabilities jetzt nach
~/.cache/agenthub/client-capabilities-<agent>.json.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 21:21:28 +02:00
chahinebrini
dbaf29a6c6 feat(mcp): Push-Kanal beim Bridge-Start binden (agenthub mcp --agent <name>)
Bisher band die Bridge den Push-Kanal erst, wenn der Agent seinen Namen nannte
(hello/work). Genau davor — direkt nach dem Session-Start — war er also
weiterhin taub, und das ist der Zustand, den wir beheben wollen. Mit
`--agent <name>` bindet die Bridge ab der ersten Sekunde.

Codex- und Kimi-Konfiguration entsprechend gesetzt (args ["mcp","--agent",…]).

Briefing umgeschrieben: Es sagte "du bist waehrend der Arbeit taub" — das
stimmt nicht mehr. Jetzt: Push-Benachrichtigungen sind verbindlich, sofort
darauf reagieren; der pending-Block im task_log-Ergebnis bleibt als zweiter
Empfangsweg, Loggen bleibt Pflicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 21:13:07 +02:00
chahinebrini
e8c2e43cb0 feat(mcp): echter Push an Agenten über MCP-Notifications — ohne blockierenden Tool-Call
DIE WURZEL, endlich an der Wurzel gefasst. Bisher konnte ein Agent nur
empfangen, WÄHREND er in `agenthub_work` blockierte. Kehrte der Aufruf leer
zurück und das Modell startete ihn nicht neu, war der Agent unerreichbar —
ein Mensch musste ihn anstoßen. Wir haben einen ganzen Tag um diese Tatsache
herum gebaut (Watchdog, Check-in-Kanal, längere Timeouts, notListening) statt
sie aufzulösen. Der CEO hat zu Recht darauf bestanden, das Protokoll selbst
zu nutzen.

MCP kann Server→Client jederzeit `notifications/message` schicken. Die Bridge
ist ohnehin ein langlebiger Prozess pro Agent: sie hängt sich jetzt dauerhaft
an den SSE-Stream des Hubs (`pushChannel.ts`) und reicht relevante Ereignisse
als Notification hoch — neue/zurückgegebene Task, Nachricht, beantwortete
Frage, Abbruch. Bindung erfolgt, sobald der Agent seinen Namen nennt
(hello/work), inklusive Alias-Auflösung; Reconnect mit Backoff.

⚠️ Die entscheidende Falle: `sendLoggingMessage` prüft `_capabilities.logging`
und verwirft die Notification sonst STILL. Der Server deklarierte gar keine
Capabilities — daran wäre dieser Weg unbemerkt gescheitert. Jetzt deklariert.

Live verifiziert mit einem echten MCP-Client: Nachricht am Hub angelegt →
Notification kam beim Client an, ohne dass dieser in einem Tool-Call wartete.

OFFEN und bewusst nicht behauptet: ob die CLI-Hosts (Codex/Kimi) die
Notification dem Modell zeigen — das ist client-abhängig und empirisch zu
prüfen. Deshalb bleiben Check-in-Kanal und Work-Loop als Netz bestehen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 21:08:21 +02:00
chahinebrini
e5bd28488d feat(work): workTimeoutSec pro Agent — codex und kimi auf dasselbe Niveau
Der Work-Loop blockierte fuer ALLE Agenten 50s. Der Wert stammt von Kimi-Code,
dessen MCP-Client Requests nach gut einer Minute abbricht (-32001). Codex
vertraegt Minuten — musste aber mit demselben Minimum leben und kehrte dadurch
sechsmal so oft leer zurueck wie noetig. Jede Rueckkehr ist eine Gelegenheit,
den Turn zu beenden und aus dem Loop zu fallen; genau daran unterschied sich
codex' Zuverlaessigkeit von kimis.

Ein globaler Default zwingt alle auf die Grenze des schwaechsten Clients. Die
Grenze gehoert aber zum Agenten, nicht zum Hub — deshalb jetzt
`agents[].workTimeoutSec` im Roster, ausgeliefert ueber /agents/:agent/identity.
Aufloesung: explizites Argument > Roster > globaler Default.

Konfiguriert: codex 240s, kimi 50s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 20:57:52 +02:00
chahinebrini
6a4cd32312 feat(pulse): notListening — Agenten mit offener Arbeit, die nicht im Loop sitzen
Der Fall, der den Menschen zum Postman macht: eine Task ist an einen Agenten
adressiert, aber er sitzt nicht in agenthub_work. Er ist dann NICHT dormant
(er lebt womöglich und hat gerade erst mit dem Hub gesprochen), hört aber
nicht zu — die Arbeit bleibt liegen, ohne dass irgendwo ein Fehler erscheint.

Real erlebt: TSK-0288 lag offen an codex adressiert, codex war `active`
(75 s zuvor gesehen) mit inLoop=false. Der Architekt hat es nicht bemerkt und
dem CEO stattdessen erzählt, die Task werde "automatisch" geclaimt. Genau
diese Erwartungs-statt-Prüfung soll das Signal künftig unmöglich machen.

`/architect/pulse` liefert dazu `notListening` mit Agent, Anzahl wartender
Tasks und einem Klartext-Hinweis.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 20:17:40 +02:00
chahinebrini
c2e19b3371 feat(watch): --ignore-from — Notifier nicht vom Watchdog zumüllen lassen
Der Architekten-Message-Notifier (`watch --await-message claude`) feuerte bei
JEDER Nachricht — auch bei den Watchdog-Erinnerungen, die alle paar Minuten
eintreffen ("review waiting 5m/10m/15m…"). Real erlebt: dreimal hintereinander
geweckt worden, ohne dass etwas Neues passiert war. Ein Signal, das im Takt
seiner eigenen Erinnerungen feuert, entwertet sich selbst.

`--ignore-from <agents>` blendet Absender aus; message-Events tragen dafür
jetzt `from` (bisher nur im title). Armung des Architekten künftig mit
`--ignore-from agenthub` — echte Agenten-Post weckt weiterhin sofort, die
Watchdog-Erinnerungen sieht man beim Pollen ohnehin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 19:47:11 +02:00
chahinebrini
7a4ccaeb61 fix(hub): dormant ≠ beschäftigt — arbeitende Agenten nicht mehr als tot melden
`dormantAgents` filterte auf `!inLoop`. `inLoop` ist aber immer false, solange
ein Agent einen Task AUSFÜHRT — dadurch meldete der Pulse jeden hart
arbeitenden Agenten als dormant. Live gesehen: codex mit 17 s altem Check-in
stand als dormant im Pulse. Genau das Signal, auf das sich der Architekt
verlassen soll, hätte ihn dazu gebracht, funktionierende Agenten anzustoßen.

Jetzt zwei getrennte Fälle:
  - `loopExitReason` gesetzt → der Agent hat den Loop ausdrücklich verlassen
    und hört nicht mehr zu: sofort melden (unverändert).
  - kein Exit-Grund, aber Task in Arbeit → er führt aus; erst nach
    DORMANT_AFTER_MS (5 min) ohne Check-in ist das mein Fall.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 19:32:10 +02:00
chahinebrini
5fb2b6d6c6 fix(hub): SessionStart-Hook und agenthub start liefern denselben Text + Verzeichnis-Guard
1) hookContext druckte einen eigenen, aelteren Text. Fuer Codex/Kimi ist der
   Hook aber oft der EINZIGE Einstieg (sie starten automatisch) — dadurch
   kannten auto-gestartete Agenten weder den Check-in-Kanal noch DEC-0035.
   Aufgefallen, als codex ohne `agenthub start` losgelaufen ist. Beide Wege
   nutzen jetzt agentBriefing().

2) `server start` prueft jetzt VOR dem Binden, ob hier ueberhaupt ein Projekt
   liegt. Vorher band der Server erst den Port und starb dann beim Laden der
   Config — der alte Hub war da schon gekillt, die Agenten liefen ins Leere.
   Klassiker: aus dem Quellrepo statt aus dem Projekt gestartet (an einem Tag
   dreimal passiert). Jetzt sofortiger, verstaendlicher Fehler.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 19:21:08 +02:00
chahinebrini
1250359e6d fix(hub): Check-in adressiert wie der Work-Loop (Titel-Präfix + Handoff)
Der Check-in prüfte nur `assignedTo`, `findAddressedOpenTask` dagegen
Titel-Präfix `<name>:`, Handoff-Empfänger UND `assignedTo`. Folge: `agenthub
start codex` meldete "AKTUELL: nichts offen", während TSK-0268 mit
`codex:`-Präfix offen lag — der Work-Loop hätte sie unmittelbar danach
geclaimt. Zwei Wahrheiten über dieselbe Frage.

Jetzt nutzt der Check-in dieselbe Adressierungslogik.

Tests: 293 → 295.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 19:18:02 +02:00
chahinebrini
4be0826a6f fix(hub): delivered-Nachrichten bleiben im Check-in sichtbar
`delivered` heißt nur "einmal ausgeliefert", nicht "gelesen" — abgeschlossen
ist eine Nachricht erst mit read/acked. Der Check-in filterte aber auf
`unread` und ließ damit jede Nachricht fallen, die irgendwo schon einmal
aufgetaucht war (etwa beim Session-Start), ohne dass der Agent sie je
beantwortet hätte.

Real aufgefallen an einer Architekten-Korrektur an kimi: Status `delivered`,
im pending-Block unsichtbar — die Korrektur wäre still verlorengegangen.
Genau der Fehlermodus, gegen den der Kanal gebaut wurde.

Tests: 292 → 294.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 19:12:23 +02:00
chahinebrini
a6f640242a fix(hub): beantwortete Asks über den Check-in-Kanal nachliefern
Realer Fall heute: kimi stellte ASK-0004 um 16:43 und blockierte in
`agenthub_ask(wait:true)`. Der Wait läuft nach 300 s aus. Der Architekt
brauchte für die Architekturentscheidung 18 Minuten — danach war kimi
dormant, das korrekt emittierte `ask`-Event lief ins Leere, und ein Mensch
musste den Agenten wecken.

Dieselbe Taubheit wie in TSK-0274, nur an einer anderen Stelle: dormant =
unerreichbar. Deshalb wandert die Antwort jetzt in den pending-Block —
`resolvePending` liefert beantwortete eigene Fragen mit, sodass sie beim
nächsten Hub-Kontakt (task_log, checkin, start) ankommt, egal wie lange der
Architekt gebraucht hat.

Tests: 290 → 292.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 19:09:41 +02:00
chahinebrini
e2bbae2044 feat(cli): agenthub start <agent> kennt den Architekten — Lagebericht statt Loop
Der Architekt kann prinzipiell nicht in agenthub_work blockieren (er ist die
interaktive Gegenstelle des Menschen). Sein Sessionstart ist deshalb ein
Lagebericht: Hub-Status, Board, wer woran arbeitet inkl. "kein Check-in seit",
wer auf Übernahme wartet, und eine ausdrückliche Warnung, wenn etwas auf sein
Review wartet. Dazu das rollenspezifische Briefing (pollen statt blockieren,
Approve ist sein Gate, Push gehört dem Menschen, "still" ≠ "tot").

BUGFIX dabei: Die Rolle wurde aus der LOKALEN Config gelesen und hing damit am
Startverzeichnis — das Quellrepo hat ein .agenthub OHNE Config, wodurch
loadConfig warf und die Auflösung stumm auf "implementer" zurückfiel. Der
Architekt bekam so das Implementer-Briefing und hätte sich einen Task
geclaimt. Jetzt kommt die Rolle vom Hub-Roster, wenn ein Hub erreichbar ist;
der lokale Weg greift nur noch ohne Server.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 18:44:15 +02:00
chahinebrini
b5533359d5 feat(cli): agenthub start <agent> — ein Einzeiler statt Copy-Paste-Prompt
Der Mensch soll pro Session eine Zeile tippen, nicht einen Textblock
einfügen (CEO: „die Prompts sind mega lang"). `agenthub start codex` druckt
jetzt das verbindliche Briefing selbst — Loop-Pflicht, Taubheit während der
Arbeit, pending-Block als einziger Empfangsweg, DEC-0035 — und zeigt
anschließend, was gerade auf den Agenten wartet.

Kein zweites Kommando: das bestehende `start` (Onboarding: announce, Task
claimen, Handoff drucken) nimmt jetzt ein positionales Agent-Argument und
stellt das Briefing voran. `--agent` bleibt für Hooks/Skripte, `--no-briefing`
gibt das alte Verhalten.

Die Rolle kommt aus dem Roster (Architekt wird als solcher erkannt), sodass
`agenthub start <name>` für jeden Agenten ohne weitere Flags reicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 18:34:48 +02:00
chahinebrini
1f3126b3b5 feat(hub): Check-in-Kanal + Review bindet den Agenten — der Wurzelfix (TSK-0274/0273, DEC-0035)
WURZEL: Ein Agent empfängt SSE-Events ausschließlich, solange er in
agenthub_work blockiert. Während er einen Task AUSFÜHRT, ist er vollständig
taub — kein Reopen, kein Cancel, keine Nachricht erreicht ihn. Ein echter
Interrupt in einen laufenden Agenten-Turn existiert nicht. Heartbeat,
Watchdog, Polling-Fallback und Presence-Tracking waren allesamt Umgehungen
dieser einen Tatsache; deshalb kam das Problem jede Session zurück.

LÖSUNG — der einzige reale Kanal: die Momente, in denen der Agent von sich
aus mit dem Hub spricht.

- checkinService.resolvePending(): zustandslos aus Tasks + Messages
  abgeleitet, nichts, was ein Neustart verliert. Erkennt, dass dem Agenten
  die Arbeit entzogen wurde (reopen/cancel/fremder Claim), und liefert
  ungelesene Nachrichten + offene Zuweisungen mit.
- agenthub_task_log gibt den pending-Block zurück; der Tool-Text macht ihn
  verbindlich (pending.interrupted ⇒ sofort aufhören, nicht einreichen).
- Neu: agenthub_checkin(agent, taskId) für lange Strecken ohne Log-Zeile,
  plus GET /agents/:agent/pending.
- resolvePending degradiert auf reinen Namensvergleich, wenn keine Config da
  ist — ein Check-in darf nie an Konfiguration scheitern.

DEC-0035: `review` bindet den Agenten wie `in_progress`. Vorher galt er in
der Sekunde des Einreichens als frei, griff sich die nächste Task, und ein
Reopen prallte danach am Ein-Task-Guard ab (so ging der Reopen von TSK-0218
verloren). Der Loop bleibt aktiv — wach warten, nichts Neues anfangen.

Außerdem: in_progress → open erlaubt, damit der Architekt eine festhängende
Arbeit überhaupt entziehen kann (vorher 400, Agent blieb dauerhaft blockiert).

SICHTBARKEIT: /health und /agent-health zeigen pro Agent pendingCount
(wartende Tasks) und deafForSec (wie lange ohne Check-in bei laufender
Arbeit). Ungelesene Nachrichten stehen separat — der dreistellige
Altbestand einzelner Agenten hätte das Signal sonst erschlagen.

Tests: 279 → 290. tests/singleClaim (b) auf den neuen Vertrag umgeschrieben
(+ (b2): ein Reopen gewinnt gegen die nächste Task).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 18:05:08 +02:00
chahinebrini
6d088648da feat(hub): Korrektheits- und Realtime-Härtung — Alias, Lifecycle, Presence, Architekten-Pulse (TSK-0242/0245/0249)
Drei vom Architekten abgenommene Tasks, gebündelt als Checkpoint:

- TSK-0242: Agent-Alias-Mapping (kimi-ah → kimi kanonisiert, Rollen → preferredAgent),
  reopenTask räumt claimedBy ab, fsWatch reindiziert direkte Datei-Edits,
  work-Default 300s → 50s, task_list mit Limit.
- TSK-0245: zwei Agent-Klassen (dispatch loop|architect). Watchdog mahnt
  architekt-getriebene Agenten nur noch EINMAL statt im Minutentakt;
  `task dispatch` startet sie explizit, `task record` trägt extern
  erledigte Arbeit mit origin=external nach.
- TSK-0249: Lifecycle wird serverseitig erzwungen (open→review scheitert mit
  klarer Meldung), claimedBy/doneBy überleben bis done, Presence pro Agent,
  Review-Watchdog, GET /architect/pulse (1.4 kB statt 34 kB, since-Cursor,
  omitted statt stillem Abschneiden), unbekannter Agent → 400 statt 500.

Alle Punkte live am laufenden Hub nachgemessen, nicht aus Agenten-Logs übernommen.
Tests: 242 → 279 grün, tsc sauber.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 17:48:24 +02:00
chahinebrini
9dfbf3f657 fix(work): single-claim semantics — max 1 in_progress claim per agent, next auto-claim only after review/done, server-side double-claim guard (TSK-0237)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 02:29:13 +02:00
chahinebrini
f5680c6982 feat(server): agent-health page + realtime auto-wake — SSE Last-Event-ID replay (durable SQLite event log), polling fallback wake on assign/message, /agent-health with reachability ampel + test-message delivery tracking + transport badge (TSK-0230)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 01:20:20 +02:00
chahinebrini
7bf52a0be3 feat(server): watchdog re-notify, agent health ampel, architect budget visibility
- Watchdog (configurable, ~60s): re-emits SSE + unread reminder + board
  warn log for unclaimed assigned tasks (3min high/critical, 10min else);
  alerts the architect on silent in_progress tasks (>15min, no auto-reassign);
  per-task cooldown against alert spam.
- GET /health (status/version/uptime/counts + per-agent traffic light) with
  lastSeen stamped on announce/claim/review/log/message; board sidebar shows
  the agent ampel; new 'agenthub health' CLI command.
- Budget: architect coordination actions (reviews, handoffs, messages,
  approvals) surface as estimated activity tokens + an 'actions' counter.
- Version bump 0.10.2 (single source: src/version.ts).
2026-07-23 17:52:37 +02:00
chahinebrini
9551c90597 fix(realtime): discovery self-heal, polling fallback, message ack semantics, CRLF SSE parser
- probe configured server URL (~1s) before remote commands; on failure
  re-discover via LAN discovery, persist and use the resolved URL
- poll tryClaim/trySurfaceMessages every ~4s during the SSE wait in CLI
  work and MCP waitForTask — a lost SSE frame costs seconds, never sleep
- work/MCP no longer auto-mark messages read: surfacing sets delivered,
  explicit ack/read required; loops still wake only on unread (no spin)
- parseSSEBuffer accepts CRLF frame separators and joins multi data: lines
- bump version to 0.10.1 (package.json, CLI --version, MCP server)
2026-07-23 16:53:33 +02:00
chahinebrini
52e5d0bf28 fix(cli): claimAndPrintTask zeigt neuesten Handoff statt ersten (Architekt-Hotfix) 2026-07-23 15:05:54 +02:00
chahinebrini
97db2358bb fix(board): kpi chips strictly one line — nowrap, compact durations (5d/3h/25m), no prefix 2026-07-22 19:10:31 +02:00
chahinebrini
c9e240b068 chore(release): v0.10.0 — board v2 glass dashboard 2026-07-22 17:38:24 +02:00
chahinebrini
6e81500b74 feat(ui): shared v2 header on all pages (logo, project cell, gradient button) 2026-07-22 17:30:09 +02:00
chahinebrini
964ddff2f2 feat(board): drop budget über-tabs, Verlauf chart lives in donut card as third tab 2026-07-22 17:19:55 +02:00
chahinebrini
5dd5806640 fix(board): card overlap via grid-auto-rows max-content + review cards get ring design 2026-07-22 17:11:19 +02:00
chahinebrini
2a57588797 fix(board): v2 column height chain — stretch main area, board flexes so card lists scroll 2026-07-20 18:11:51 +02:00
chahinebrini
2b742706f2 docs(board): v2 redesign spec + implementation plan 2026-07-20 17:45:05 +02:00
chahinebrini
b09e4c78d2 feat(board): replace v1 monolith with modular v2 glass dashboard 2026-07-20 17:44:45 +02:00
chahinebrini
30b889acd9 feat(board): v2 sidebar with tabbed budget card, throughput chart, live feed 2026-07-20 17:31:58 +02:00
chahinebrini
9553461130 feat(board): v2 kpi cards (area chart, capped chips, done bar) 2026-07-20 17:28:04 +02:00
chahinebrini
e5071f8750 feat(board): v2 header, splash screen, logo route 2026-07-20 17:26:25 +02:00
chahinebrini
4c46e361a9 feat(board): v2 glass design system css 2026-07-20 17:23:54 +02:00
chahinebrini
a34d0ea870 feat(board): v2 viewmodel helpers + logo asset 2026-07-20 06:00:31 +02:00
chahinebrini
f9c7c2ae3c fix(board): stop board twitch + card overlap; KPI pulse; SVG donut center (TSK-0153/0154)
Root cause of twitch + overlap: source.onmessage ran an unconditional full
board rerender (cardsEl.innerHTML=) on EVERY change event, and the enter
animation was on a global `.card` selector so it replayed on all cards each
rerender.

1+2) SSE onmessage now parses event.type and only rerenders the board on
     type==='task' (message/ask/decision/memory/handoff/agent no longer
     trigger a board rerender; budget still refreshes — it has its own
     diff/throttle). renderBoard() rebuilt as a per-card diff-patch
     (patchColumn/cardSig/cardNode): untouched cards are kept + reordered,
     only new/changed cards are (re)built. Enter animation scoped to a
     one-shot .card-new class. min-height added to the .card transition list
     so the console-open growth is smooth. Open live consoles preserved
     (reapplyConsoles unchanged; kept nodes retain their console DOM).
3) setMetric() pulses the .metric-card (.metric-flash + @keyframes metricFlash)
     only when the value actually changes from a previously-seen value.
4) Donut center readout moved from a CSS top:66.7% div to SVG-native <text>
     in viewBox coords (x=110, value y=102, label y=120) so it scales and sits
     in the arc opening. .donut-value/.donut-label restyled with fill.
     TSK-0154 politur: .cc-line.level-bridge .cc-text color.

tsc clean; vitest 36 files / 184 tests green. Visual points are Screenshot/
manual (no board.test.ts). No console regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 02:21:41 +02:00
chahinebrini
08667637e6 fix(agenthub): TSK-0007 — FTS5 duplicate-on-update + claimTask race-guard
- core/index.ts: the FTS5 'search' table has no UNIQUE on id, so INSERT OR
  REPLACE appended a new row every update and the search JOIN returned an id
  N times. Now delete-then-insert by id -> exactly one FTS row per entity.
- taskService.claimTask: race-guarded — only an OPEN task can be claimed;
  same-agent re-claim is an idempotent no-op; a task claimed by another agent or
  past open is refused. getTask+updateTask are sync, so within the single-thread
  event loop the read-check-write is effectively atomic (first claim wins).
- routes.ts (spec 'nicht brechen'): PATCH in_progress surfaces a lost/non-open
  claim as a clean 400 instead of 500, so the board drag reverts gracefully and
  a second agent can't clobber the first's claim.
- tests: +coreCorrectness.test.ts (no FTS dup after updates; concurrent claims
  -> one wins; idempotent re-claim; refuse non-open) + server.test.ts route guard

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 01:24:34 +02:00
chahinebrini
f0c7be25f4 feat(agenthub): TSK-0118 — Ask primitive for autonomous decision-routing
- New Ask entity (ASK-####): schema + counter + paths(EntityType 'asks') +
  events(AgentHubEventType 'ask') + fsWatch(WATCHED + toEvent). Generic entities
  table, no migration.
- askService: createAsk routes to config.roles.architect.preferredAgent, NEVER
  the CEO (a to='ceo' is rerouted); answerAsk / escalateAsk (escalatedTo='ceo',
  single channel) / getAsk / listAsks. Authority-policy JSDoc.
- Asks kept OUT of FTS5: Index.upsert gains a { fts?: boolean } option; askService
  upserts with fts:false, so 'memory search' never returns asks.
- routes: POST/GET /asks, GET /asks/:id, POST /asks/:id/{answer,escalate}, each
  emitChange type:'ask'.
- CLI 'ask <q> --from [--task][--wait][--timeout]' (SSE reconnect wait until
  status!=pending) + ask list/answer/escalate; remoteClient ask methods.
- MCP agenthub_ask (wait via waitForTask, now woken by 'ask' events) +
  agenthub_ask_list/answer/escalate; agenthub_work architect branch surfaces
  pending asks ({reviews,asks,messages}).
- Unattended mode (invocation flag): work.ts ctx + CLI 'work --unattended' +
  agenthub_work schema + LOOP reminder ('call agenthub_ask instead of pausing').
- tests: +askService.test.ts (routing/answer/escalate/list/FTS-exclusion),
  +ask-wait.test.ts (routes roundtrip + SSE wait: answer resolves, no-answer
  times out cleanly)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 01:19:42 +02:00
chahinebrini
941c20d12a fix(agenthub): TSK-0119 — implementer stays reachable after a review submit (dormancy fix)
- work.ts: the CLI work loop now wakes on architect follow-up MESSAGES, not just
  tasks. drainAgentMessages() surfaces + marks-read the agent's inbox; wired into
  waitAndClaim (SSE message events) and as an upfront check in workAgent. reopen +
  next-assign already wake via task events; a plain message no longer leaves the
  re-armed loop dormant.
- mcp/server.ts: implementer agenthub_work LOOP reminder now explicitly instructs
  re-arming after agenthub_task_review so the architect's approval/reopen/message
  wakes it (waitForTask already wakes on task+message).
- tests: work.test.ts +message-wake +reopen-wake

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 01:08:59 +02:00
chahinebrini
2c2193ec55 feat(agenthub): TSK-0074 — automatic agent progress logging to a task live console
- routes PATCH /tasks/🆔 uniformly appendTaskLog + publishLog for every status
  transition (claim/review/done/cancel/reopen) and on assign; best-effort, never
  fails the mutation. No double-emission (log rides the 'log' channel; fsWatch
  only watches .md so the .log append is not re-emitted).
- MCP agenthub_task_log tool + remoteClient.appendTaskLog + CLI 'task log <id>
  --text [--agent][--level]'
- agenthub_work LOOP reminder: 'Report meaningful progress with agenthub_task_log'
- taskDetail: Live Console panel — historic lines via readTaskLog + live tail via
  the named task-log SSE event filtered to this task id
- tests: +taskLog-live.test.ts (PATCH->one log event, no double change, historic
  render, POST /log, remoteClient)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 01:02:04 +02:00
chahinebrini
3f1fb76a84 feat(agenthub): TSK-0120 — split messaging from /activity into a /messages conversation view
- MessageSchema: status enum unread|delivered|read|acked + replyTo (additive, RW-compatible)
- messageService: listInbox transitions unread->delivered on fetch (agent-scoped);
  markMessageDelivered (idempotent, never downgrades); ackMessage(cwd,id,by?)
- routes: GET /messages HTML branch -> renderMessagesHtml; GET /messages/:id;
  POST /messages/:id/ack (mirror of /read, emits message/updated status=acked)
- server/messages.ts: 2-column conversation view (list grouped by pair + unread badge,
  thread bubbles aligned by ?as=, replyTo indentation, TSK pill, live via /events)
- activity.ts: drop message rows (tasks-only hard separation)
- ui-shared: HeaderPage +messages + nav link
- CLI: message ack <id> [--by], message reply <parentId> --from --text [--task];
  remoteClient ackMessage + getMessage
- tests: +message-receipts.test.ts, server.test.ts activity/messages/receipt-chain

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 00:56:16 +02:00
chahinebrini
7922eeb8e8 chore(agenthub): snapshot pre-existing orphaned WIP (single-instance/mDNS + claimedBy + message-read) as batch baseline 2026-07-12 00:46:47 +02:00
chahinebrini
2fb9db15b1 feat(board): v3 kanban + animated Lottie KPI icons
Board-v3 (TSK-0099, codex, architect-reviewed 7/7):
- kanban columns fill viewport height, scroll inside the column (no page scroll)
- padding/margin between task cards
- bar-chart HTML-escape bug fixed (est marker inserted raw, not esc())
- half-donut center text centered
- task detail opens in a modal (title/description/activity, empty sections hidden)
  instead of navigating to /tasks
- header-jitter on page switch fixed via scrollbar-gutter: stable
- SSE connection-leak/freeze fixed (EventSource closed on pagehide/beforeunload)

Animated KPI icons (architect):
- vendored lottie-web player + Lottie card icons under assets/, served via a
  path-traversal-guarded /card-assets/* route (offline-safe, no CDN)
- state-driven: loop-while-active (open/in_progress/review), pulse-on-change
  (active/done/agents); light KPI card bg so the black-line icons read clearly

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMN96FH9rJDY2bZnEBdPfA
2026-07-09 00:09:55 +02:00
chahinebrini
1749f1c58c feat(board): v2 — drop handoffs/decisions panels, tabbed+throttled company donut, real-vs-estimated agent bars with FLIP reorder, session/total reset, fixed-height scrollable columns
codex TSK-0097 board redesign v2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:11:38 +02:00
chahinebrini
0884032db1 feat(board): session batch — delete + unified fixed header + in-card live console + reviewer field + 3/4 kanban token-insights redesign
Delete endpoint + trash UI, shared header across all pages, task-log SSE + in-card console, reviewer separate from assignee (board+team), codex board redesign (3/4 kanban + company half-donuts + per-agent bars + square cards).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:43:42 +02:00