- Remove tool_choice from AgentEndpoint (create, update, list responses)
- ChatService: automatically set tool_choice='any' when workspace, memory
or skills present, null otherwise. Same for preparer/review/finalizer
- SystemPromptBuilder: remove 3 toolAny-conditional prompt blocks
(KRITISCH, Verhalten, ERINNERUNG)
- Frontend: remove checkbox, badge and JS references
- Agent.ToolChoice DB field preserved for backward compat, no longer read
Wenn eine Telegram-Nachricht von einem nicht verknüpften Chat eingeht:
- Sucht nach Sessions mit diesem Bot, die noch keine Chat-ID haben
- Genau 1 unverknüpfte Session → automatisch zuweisen + Bestätigung
- 0 oder mehrere → Chat-ID anzeigen, manuell im Web-UI zuweisen
Neue DB-Methode: FindUnlinkedSessionsForBot(botId)
Der Tag-Filter in BuildHistoryFromDb filterte alle Messages mit Tag
aus, sofern der Tag nicht dem angeforderten includeTag entsprach.
openclaw-import ist jedoch kein Sub-Agent-Tag (wie preparer/review/
finalizer), sondern ein Herkunfts-Tag und muss immer in der History
enthalten sein.
- AgentRecord: neues IsPublic-Feld mit DB-Migration
- GET /api/agents/available: eigene + public Agents (alle Properties + isOwner)
- GET /api/telegram/bots/available: alle Properties außer Token/Secret + sessionCount
- Agent-Page: Public-Checkbox (andere User können Agent in Sessions verwenden)
- Dashboard: agentMap aus availableAgents gebaut (public Agents fremder User auflösbar)
- Session-Page + Cronjob-Page: Agent-Picker nutzt /available statt /agents
- Agent-Cards: Public- und Fremd-Badges
- Fix: Chat-Button bei Sessions mit public Agent eines anderen Users nicht mehr disabled
Backend:
- New OpenClawEndpoint with GET /api/openclaw/sessions and POST /api/openclaw/import
- Lists all OpenClaw sessions with preview messages, model, summary
- Imports user/assistant text messages (skips tool-use/tool-result)
- Cleans user messages (removes webchat prefixes, cron tags, conversation info blocks)
- Re-import support: deletes previous openclaw-import chains before importing
- New DatabaseService methods: CreateChainForImport, CreateMessageForImport, DeleteOpenClawImport
- Added SortKey to MessageRecord (computed: openclaw-import gets id-10B, else id)
History sorting:
- OpenClaw-imported messages always appear before native messages
- Sort by CASE WHEN tag='openclaw-import' THEN id-10000000000 ELSE id END
- Cursor-based paging updated from beforeId/afterId to beforeSortKey/afterSortKey
- All history queries (GetLatestActive, Before, After, CountBefore) updated
Frontend:
- Dashboard: new 'OpenClaw Import' menu item in session card dropdown
- Modal: shows OpenClaw sessions with metadata, summary, preview messages
- Imported messages display blue 'OC' badge in chat
- API functions: apiGetOpenclawSessions, apiImportOpenclawSession
- History paging updated to use sortKey cursors
BuildReviewMessages() komplett umgebaut:
- Tool-Calls und Tool-Results werden nicht mehr übergeben
- Nur noch die reine Text-Konversation (user/assistant) wird extrahiert
- Aufeinanderfolgende Messages gleicher Rolle werden zusammengeführt
- Korrekte user/assistant-Abwechslung sichergestellt
- Fallback wenn nur Tool-Calls ohne Text vorhanden
- Unnötigen Serialisierungs-Roundtrip für Rollen-Prüfung entfernt
Erwartete Token-Ersparnis: 50-100k+ Input-Tokens pro Review-Runde
bei typischen Worker-Sessions mit vielen write_file/edit_file-Calls.
ARCHITECTURE.md:
- Inhaltsverzeichnis-Nummerierung korrigiert (22-28 statt doppelt 20-21)
- ServerSettingsEndpoint.cs in Projektstruktur ergänzt
- Tabellenbeschreibungen vervollständigt (users, agents, sessions, chains,
messages, cronjobs) mit allen fehlenden Feldern
- ChainRecord/MessageRecord/SessionWithStats Felder aktualisiert
- AutoCommit mention-Beschreibung korrigiert
ARCHITECTURE_QUICK.md:
- ToolService 12 → 13 Tools korrigiert
- Tabellenbeschreibungen analog zur Hauptdoku aktualisiert
Nach dem Streaming werden alle .streamed Elemente entfernt und pollHistory()
rendert die Messages neu aus der DB. Bisher wurden Review-Agent-Messages dabei
als einzelne Bubbles dargestellt (jeder Text-Block + jeder Tool-Call separat),
statt als eine zusammenhängende Review-Bubble wie beim Streaming.
Lösung: _renderMessagesIntoFragment() erkennt jetzt Messages mit tag-Feld
(review/preparer/finalizer) und gruppiert aufeinanderfolgende Messages mit
demselben Tag. _renderReviewGroup() erzeugt daraus eine einzige Review-Bubble
mit Header (Icon + Agent-Name), Tool-Use-Indikatoren und zusammengefasstem
Text-Content - identisch zur Streaming-Darstellung.
Fallback für alte Messages ohne tag-Feld bleibt bestehen (_isReviewMessage).
- esc() um "→" Escaping erweitert (Bug: Anführungszeichen in value="..."-Attributen brachen HTML)
- cache_control: ephemeral zu RunAgentChat() hinzugefügt (Preparer/Review/Finalizer hatten kein Prompt Caching)
- cache_control: ephemeral zu ChatNonStreaming() hinzugefügt (Telegram hatte kein Prompt Caching)
- Trailing-Leerzeile in commands-ui.js bereinigt
- ImageService: PNG-Encoder durch WebP-Encoder ersetzt (Quality 80)
- Bilder werden jetzt immer zu WebP konvertiert (auch kleine)
- DB-Migration: Bestehende PNG/JPEG-Avatare und Session-Images
automatisch zu WebP konvertiert beim Start
- Migration ist idempotent (überspringt bereits konvertierte Bilder)
- Kein Frontend-Code betroffen (Data-URLs sind format-agnostisch)
- Archive messages (loadLatestArchive) now render reply, delete, mark
and share buttons, matching the behavior of _renderMessagesIntoFragment
- Add review-bubble and preparer-bubble detection for archive messages
- Add data-role, data-timestamp attributes for proper reply quoting
- Add isMarked/deletedOn CSS class handling for archive messages
- Also add [Preparer:] to isReviewMsg check in _renderMessagesIntoFragment
for consistency
Kernproblem: Claude vergisst [FORWARD]-Tag → Preparer leitet nie weiter.
Lösung - Logik umgedreht:
- Jede Antwort wird automatisch an Session-Agent weitergeleitet (Default)
- Nur bei explizitem [QUESTION]-Tag wird gestoppt (Rückfrage an User)
- [FORWARD] wird weiterhin erkannt und entfernt, ist aber optional
Weitere Fixes:
- Preparer bekommt eigene History mit includeTag='preparer',
damit er den Kontext seiner vorherigen Rückfragen sieht
- Session-Agent bekommt saubere History ohne Sub-Agent-Messages
(nach Preparer-Phase frisch aus DB geladen)
- [QUESTION] wird im Frontend-Markdown ausgeblendet
- MessageRecord: neues Tag-Feld (null=normal, preparer/review/finalizer=Sub-Agent)
- DatabaseService: Migration tag-Spalte, INSERT-Statements erweitert
- AnthropicService: RunAgentChat setzt Tag=agentType bei allen 4 SaveMessage-Aufrufen
- ChatService: BuildHistoryFromDb filtert getaggte Messages aus API-History
Sub-Agent-Messages bleiben in der DB (Frontend-Anzeige), werden aber
nie an die Claude API geschickt – löst das Problem für Preparer,
Review und Finalizer gleichermaßen.
- Neues optionales Session-Feld 'preparerAgentId' (DB + Model + API)
- PreparerContext im ChatContext für Agent-Konfiguration
- RunPreparer() in AnthropicService: führt Preparer vor dem Worker aus
- [FORWARD] → aufbereiteter Prompt wird an Session-Agent weitergegeben
- Ohne [FORWARD] → Rückfrage an User, Flow stoppt (preparer_query)
- Preparer-System-Prompt: erklärt Rolle, [FORWARD]-Mechanismus, Vorarbeit
- Preparer kann eigene Tools + Fähigkeiten aktiv nutzen
- ChatEndpoint: Preparer-Phase vor StreamChat integriert
- Frontend: SSE-Events (preparer_start/text/tool_use/done)
- Preparer-Bubble (Info-Farbe) mit Status Weitergeleitet/Rückfrage
- [Preparer: Name] → hübscher Header, [FORWARD] ausgeblendet
- [Vorbereitet von Name] → dezenter Header in User-Bubble
- Agent-Picker im Session-Form (vor Review-Agent)
- SystemPromptBuilder.BuildBlocks/Build bekommt planMode-Parameter
- Im Plan-Modus: Tools-Block zeigt nur Read-only-Tools (read_file, list_files, glob, grep)
- Im Plan-Modus: KRITISCH, Verhalten, Erinnerung und Git-Blöcke werden übersprungen
- Im Plan-Modus: Skills als 'nicht ausführbar' markiert
- Plan-Modus-Block zentral im SystemPromptBuilder statt doppelt in AnthropicService + SessionEndpoint
- ChatService reicht planMode an SystemPromptBuilder durch
- SessionEndpoint reicht planMode Query-Parameter an BuildBlocks durch
- System-Prompt-Modal zeigt korrekte Blöcke je nach Plan-Modus-Status
- Toggle-Button (🗺️) im Chat-Input-Bereich neben Explorer
- Plan-Modus wird per ChatRequest.planMode an Backend gesendet
- Backend: Write-Tools gefiltert (nur read_file, list_files, glob, grep,
todo_write, memory_*, ask_user_choice bleiben), System-Prompt-Block
ergänzt der Änderungen verbietet, tool_choice nie 'any' erzwungen
- ChainRecord.PlanMode: pro Chain gespeichert (informativ für History)
- Chain-Meta-Badge '📋 Plan-Modus' bei Plan-Chains
- Choice-Response erbt planMode vom vorherigen Chain
- CSS: .plan-mode-active Styling (Primary-Color Highlight)
- Placeholder wechselt zu 'Plannen...' wenn aktiv
- Plan-Modus wird bei Session-Wechsel zurückgesetzt
- Dashboard: 'Rückfragen'-Badge (🔵) bei Agent-Card wenn allowUserChoice aktiv
- SystemPromptBuilder: Hinweis-Block der Claude anweist, ask_user_choice sparsam zu nutzen
- Neues Feld AllowUserChoice (bool, default true) im AgentRecord
- DB-Migration: allow_user_choice Spalte mit DEFAULT 1
- Agent-Form: Checkbox 'Rückfragen erlauben' nach 'Tool-Nutzung erzwingen'
- ToolService.GetToolDefinitions: neuer Parameter includeUserChoice
- ask_user_choice Tool wird nur eingebunden wenn Agent es erlaubt
- Review-Agents und Non-Streaming (Cronjobs) erhalten nie ask_user_choice
- ChatEndpoint reicht Agent.AllowUserChoice an StreamChat durch
Neues Tool 'ask_user_choice' analog zum AskUserQuestion aus dem Claude Agent SDK.
Claude kann dem User Multiple-Choice-Fragen stellen, wenn es mehrere sinnvolle
Wege gibt. Der User wählt per Button, Claude arbeitet mit der Antwort weiter.
Backend:
- ToolService: ask_user_choice Tool-Definition (question + choices[2-5])
- AnthropicService/ProcessStream: Tool wird NICHT ausgeführt, sondern als
SSE-Event 'user_choice' an den Client gesendet. stopReason wird auf
'awaiting_user_choice' gesetzt (mit Guard gegen Überschreibung durch
message_delta)
- ChatEndpoint: Erkennung von Choice-Responses (choiceToolCallId + choiceResult),
tool_result wird in die korrekte Chain gespeichert (wo der tool_use liegt),
toolChoice wird auf 'auto' gesetzt damit Claude frei antworten kann
- ChatService: FindChainWithToolCall() zum Auffinden der richtigen Chain,
BuildHistoryFromDb() erweitert um Choice-Chain-Erkennung (wenn letzte Chain
leer/nur tool_result → vorletzte Chain vollständig rekonstruieren)
- ChatModels: ChatRequest um ChoiceToolCallId + ChoiceResult erweitert
Frontend:
- api.js: apiChatChoice() für Choice-Responses
- chat.js: handleStreamEvent() als shared Handler extrahiert (sendMessage +
handleChoiceClick nutzen denselben Code), handleChoiceClick() startet neuen
Stream nach Button-Klick
- ui.js: addChoiceBubble() (live/interaktiv) + addChoiceBubbleStatic() (History)
- history.js: Choice-Bubbles in _renderMessagesIntoFragment + renderMessages,
Voraus-Suche nach zugehörigem tool_result, unbeantwortet = klickbare Buttons
- chat.css: Styling für Choice-Bubbles (Primary-Color-Outline-Buttons, Hover,
Selected/Unselected States)
Robustheit:
- Backend-Fallback wenn Claude choices als String statt Array sendet
- Frontend-Guard: Array.isArray(choices) an allen 4 Render-Stellen
- Page-Refresh mit offener Choice → aktive Buttons werden korrekt gerendert
- history.js: _mergeConsecutiveAssistantTexts überspringt web_search_result
Messages beim Suchen des vorherigen Text-Blocks, sodass Text vor und
nach einer Web-Search in der History als eine Bubble gerendert wird
- Text-Blöcke werden mit doppeltem Zeilenumbruch statt Leerzeichen
zusammengefügt (besseres Markdown-Rendering)
- chat.css: web_search Tool-Box von Opacity-Regel ausgenommen
- chat.js: _afterWebSearch Flag trackt ob gerade eine Web-Search beendet wurde
- text_start nach Web-Search: resumeStreamBubble() statt neue Bubble
- web_search_start: Bubble NICHT beenden (bleibt finalisiert im DOM)
- web_search_end: setzt _afterWebSearch=true
- Flag wird bei tool_start, review_start, finalizer_start, error, done zurückgesetzt
- ui.js: Neue resumeStreamBubble() Funktion
- _lastStreamDiv/_lastStreamText speichern letzte finalisierte Bubble
- resumeStreamBubble() reaktiviert die letzte Bubble zum Weiter-Streamen
- Verhindert separate Message-Blöcke bei mehrfachen Web-Searches
- Program.cs: Port aus Config lesen mit Fallback 19000
- appsettings.json: Port: 19000 als Default
- ServerSettingsEndpoint: Port als erste Einstellung (mit Neustart-Hinweis)