enhancement
major
minor
defect
critical
major
minor
major
#30143
TL Chat: Während ein Agent mehrere Schritte ausführt, bleibt sein letzter Text sichtbar und die Laufzeile nennt den aktuellen Schritt, statt nur "…" und "Arbeitet …" zu zeigen
Kontext
Ein Agent, der in einer Benutzer-Runde viele Modellanfragen stellt (denken → Werkzeug → denken → Werkzeug …), zeigt im Chat (tl-chat, Darstellung trace="summary") während der ganzen Arbeit fast nichts: Unter der Laufzeile „Arbeitet …“ stehen nur die drei Punkte der wartenden Antwort, oft minutenlang. Texte, die der Agent zwischendurch schreibt, und sein Nachdenken tauchen kurz auf und verschwinden sofort in eingeklappten Einträgen „Ausgabe“/„Nachdenken“ des Verlaufs, sobald der nächste Werkzeugaufruf (z. B. Bash) beginnt. Beobachtet beim Coding-Agent der Steueranwendung tl-factory (Claude Code im Workspace), betrifft aber jeden mehrstufigen Agenten in tl-chat.
Ursache (tl-chat/ui/src):
- Jede Modellanfrage beginnt mit turn_started; chat-store.ts (on('turn_started', …)) legt dafür eine neue, leere Antwortnachricht an (ADD_ASSISTANT mit text: '').
- lastAnswer (thread.tsx) nimmt die jüngste assistant_text-Nachricht, also die gerade angelegte leere. Damit ist pending = busy && !answer?.text wahr, und PendingAnswer zeigt die Punkte; die Texte früherer Anfragen sortiert traceEvents in den Verlauf, wo TraceEntry eingeklappt startet.
- Die Laufzeile (RunSummary, currentThought) zeigt „Nachdenken: …“ nur, solange der letzte Schritt ein Nachdenken ist; während eines Werkzeugaufrufs zeigt sie „Arbeitet …“ ohne zu sagen, welches Werkzeug was tut. Einträge eines Werkzeugaufrufs im Verlauf haben keine Vorschau, obwohl z. B. das Bash-Werkzeug von Claude Code mit jedem Aufruf eine description schickt.
Verhalten
- Der letzte Text bleibt sichtbar: Solange die Runde läuft, gilt eine leere Antwortnachricht nicht als Antwort. Als Antwort steht der jüngste nicht leere Text der Runde, bis ein neuerer Text ihn ersetzt; der ersetzte wandert wie bisher in den Verlauf. Die wartenden Punkte erscheinen nur, solange die Runde noch gar keinen Text hat. Nach dem Ende der Runde ist die Antwort wie bisher ihr letzter Text.
- Die Laufzeile nennt den aktuellen Schritt: während des Nachdenkens wie bisher „Nachdenken: <Anfang des neuesten Absatzes>“, während eines Werkzeugaufrufs „<Werkzeug>: <Vorschau>“, z. B. Bash: Build with tests. Die Vorschau ist die description des Aufrufs, wenn er eine hat, sonst sein erstes Argument, gekürzt wie beim Nachdenken. „Arbeitet …“ bleibt nur für die Zeit ohne erkennbaren Schritt.
- Werkzeug-Einträge im Verlauf zeigen eingeklappt dieselbe Vorschau wie „Nachdenken“ und „Ausgabe“.
- Reihenfolge im Verlauf: Das Nachdenken einer Modellanfrage steht vor ihrem Text, live und nach dem Neuladen.
- Gilt live und nach dem Neuladen einer laufenden Unterhaltung (ein Fenster, das einem Lauf erst später beitritt) gleich.
Akzeptanz
- UI-Tests (thread/`chat-store`): eine Runde mit Text → Werkzeug → turn_started ohne Text zeigt den Text als Antwort und keine Punkte; eine Runde ohne jeden Text zeigt die Punkte; nach dem Ende ist die Antwort der letzte Text.
- Laufzeile während eines Werkzeugaufrufs mit description, mit nur einem Argument und ohne Argumente.
- Prüfung im Browser mit einem mehrstufigen Agenten (z. B. tl-factory, Workspace-Agent mit einer Aufgabe, die bauen muss).
Lösung
Browser-Teil von tl-chat (ui/src):
- lastAnswer (thread.tsx) überspringt während einer laufenden Runde leere assistant_text-Nachrichten und liefert den jüngsten nicht leeren Text. Die Punkte (PendingAnswer) erscheinen nur ohne jeden Text. Da die Regel nur die Ereignisliste liest, gilt sie live und nach dem Neuladen gleich.
- toolPreview(call): das Argument description, falls ein String, sonst der Wert des ersten Arguments (ein String direkt, eine Liste einfacher Werte als durch Komma getrennte Werte, z. B. die Ids von getInstance, sonst JSON), sonst leer; Leerraum zusammengefasst, auf 80 Zeichen gekürzt. Die Argumente werden mit derselben Funktion gelesen wie in der Freigabekarte (parseArguments in prompts.tsx), also auch aus dem JSON-Text des Streams.
- currentStep (statt currentThought): Nachdenken → „Nachdenken: …“, aber nur bis der Text derselben Anfrage zu streamen beginnt; laufender Werkzeugaufruf → „<Werkzeug>: <Vorschau>“ bzw. nur der Werkzeugname ohne Vorschau; sonst „Arbeitet …“.
- Werkzeug-Einträge im Verlauf erhalten eingeklappt die Vorschau aus toolPreview.
- reducer.ts (APPEND_THINKING): Eine Modellanfrage legt ihre Antwortnachricht beim Start an und denkt, bevor sie schreibt; das Nachdenken wird deshalb vor der noch leeren Antwortnachricht eingefügt.
Server (tl-ai-core):
- ConversationLogListener schrieb an jeder Grenze (Werkzeugaufruf, Freigabe, Ende der Runde) erst den gepufferten Text, dann das gepufferte Nachdenken in den Verlauf - auch in die Teile einer Delegation. Er schreibt jetzt das Nachdenken zuerst. Damit stimmt die Reihenfolge nach dem Neuladen, und ConversationLogReplay gibt dem Modell eine Antwort wieder in der Reihenfolge, in der es sie erzeugt hat (Nachdenken vor Text). Bereits gespeicherte Verläufe behalten ihre alte Reihenfolge.
Geprüft im Browser mit dem Chat von tl-ai-demo: synchrone Delegation an einen Unteragenten (Laufzeile „wait_for_subagent: sa-1“ für die Dauer der Wartezeit), getInstance-Aufrufe („getInstance: GenericObject:2916“), Neuladen während eines Laufs, Reihenfolge des Verlaufs live und nach dem Neuladen.