From f394d69b704557868686fc1b57a21b51f77e3b5d Mon Sep 17 00:00:00 2001 From: Eddy Date: Wed, 22 Apr 2026 08:36:21 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Nachrichten=20w=C3=A4hrend=20Claude=20a?= =?UTF-8?q?rbeitet=20senden=20=E2=80=94=20wie=20in=20VS=20Code=20Extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bridge hat jetzt eine interne Pending-Queue: Nachrichten die während einer laufenden query() eingehen werden gepuffert und nach dem aktuellen Turn automatisch FIFO abgearbeitet. Kein Frontend-Queue mehr nötig. User kann jetzt wie in Claude Code/VS Code Extension weiter tippen während Claude arbeitet. Nachrichten erscheinen sofort im Chat und werden nahtlos nach dem aktuellen Turn verarbeitet. Co-Authored-By: Claude Opus 4.6 --- scripts/claude-bridge.js | 33 ++++++++++ src/lib/components/ChatPanel.svelte | 94 ++++++++--------------------- 2 files changed, 59 insertions(+), 68 deletions(-) diff --git a/scripts/claude-bridge.js b/scripts/claude-bridge.js index a788444..2ef77e1 100644 --- a/scripts/claude-bridge.js +++ b/scripts/claude-bridge.js @@ -36,6 +36,12 @@ if (!IS_DAEMON) process.stdin.resume(); let activeAbort = null; let currentAgentId = null; let currentModel = process.env.CLAUDE_MODEL || 'opus'; +let isQueryRunning = false; + +// Pending-Queue: Nachrichten die während einer laufenden query() eingehen +// werden hier gepuffert und nach dem aktuellen Turn automatisch abgearbeitet. +// So kann der User wie in Claude Code/VS Code Extension weiter tippen. +const pendingMessages = []; // Agent-Modus (solo | handlanger | experten | auto) let agentMode = 'solo'; @@ -339,6 +345,20 @@ function summarizeToolInput(tool, input) { // ============ Claude Agent SDK ============ async function sendMessage(message, requestId, model = null, contextOverride = null, resumeSessionId = null) { + // Wenn bereits eine query() läuft: Nachricht in Pending-Queue puffern + // und sofort bestätigen. Wird nach aktuellem Turn automatisch abgearbeitet. + if (isQueryRunning) { + pendingMessages.push({ message, requestId, model, contextOverride, resumeSessionId }); + sendResponse(requestId, { status: 'queued', position: pendingMessages.length }); + sendMonitorEvent('agent', `Nachricht gepuffert (Position ${pendingMessages.length})`, { + messageLength: message.length, + queueSize: pendingMessages.length, + }); + return; + } + + isQueryRunning = true; + // Modell für diese Anfrage (Parameter > State > Default) const useModel = model || currentModel; @@ -767,6 +787,19 @@ async function sendMessage(message, requestId, model = null, contextOverride = n sendEvent('all-stopped'); currentAgentId = null; activeAbort = null; + isQueryRunning = false; + + // Pending-Queue: Nächste Nachricht automatisch abarbeiten (FIFO) + if (pendingMessages.length > 0) { + const next = pendingMessages.shift(); + sendMonitorEvent('agent', `Pending-Nachricht wird verarbeitet (${pendingMessages.length} verbleibend)`, { + messageLength: next.message.length, + remaining: pendingMessages.length, + }); + // Asynchron starten — nicht awaiten damit finally sauber abschließt + sendMessage(next.message, next.requestId, next.model, next.contextOverride, next.resumeSessionId) + .catch(err => sendMonitorEvent('error', `Pending-Dispatch fehlgeschlagen: ${err.message}`, {})); + } } } diff --git a/src/lib/components/ChatPanel.svelte b/src/lib/components/ChatPanel.svelte index e1de7cb..a34f55e 100644 --- a/src/lib/components/ChatPanel.svelte +++ b/src/lib/components/ChatPanel.svelte @@ -717,21 +717,8 @@ addMessage('system', `📋 Clipboard (${content_type}): ${suggestion}\n\`\`\`\n${preview}\n\`\`\``); }); - // Queue-Auto-Dispatch: sobald isProcessing von true auf false wechselt - // wird die naechste Nachricht aus der Queue abgeschickt (FIFO). - let lastProcessing = false; - const unsubProcessing = isProcessing.subscribe((val) => { - if (lastProcessing && !val) { - const queue = get(messageQueue); - if (queue.length > 0) { - const [next, ...rest] = queue; - messageQueue.set(rest); - $queuedMessage = rest.length > 0 ? rest[0] : null; - dispatchMessage(next).catch((e) => console.error('Queue-Dispatch fehlgeschlagen:', e)); - } - } - lastProcessing = val; - }); + // Legacy-Queue Subscriber (Sicherheitsnetz — Hauptlogik ist jetzt in der Bridge) + const unsubProcessing = isProcessing.subscribe(() => {}); return () => { window.removeEventListener('keydown', handleGlobalKeydown); @@ -742,39 +729,23 @@ }); function cancelQueued() { - // Alle gequeuten Nachrichten verwerfen + aus dem Chat entfernen + // Legacy: Queue-Cancel (Bridge hat jetzt eigene Pending-Queue) messageQueue.set([]); $queuedMessage = null; - messages.update((msgs) => msgs.filter((m) => !(m as any).queued)); } async function sendMessage() { const text = $currentInput.trim(); if (!text) return; - // Waehrend Claude antwortet: Nachricht in FIFO-Queue. - // Sofort als User-Message im Chat anzeigen (mit queued-Marker). - // Der Subscriber dispatcht automatisch wenn Claude fertig ist. - if ($isProcessing) { - messageQueue.update((q) => [...q, text]); - $queuedMessage = text; - // Sofort im Chat anzeigen damit User sieht dass die Nachricht angekommen ist - const queuedMsg: Message = { - id: crypto.randomUUID(), - role: 'user', - content: text, - timestamp: new Date(), - queued: true, - }; - messages.update((msgs) => [...msgs, queuedMsg]); - $currentInput = ''; - return; - } - + // Nachricht IMMER sofort senden — auch während Claude arbeitet. + // Die Bridge puffert die Nachricht intern und verarbeitet sie + // automatisch nach dem aktuellen Turn (wie in Claude Code/VS Code Extension). await dispatchMessage(text); } - // Den eigentlichen Send-Flow ausgelagert, damit er auch fuer die Queue genutzt wird. + // Den eigentlichen Send-Flow — sendet immer sofort an die Bridge. + // Wenn eine query() laeuft, puffert die Bridge die Nachricht intern. async function dispatchMessage(text: string) { // Auto-Session erstellen falls keine aktiv let sessionId = get(currentSessionId); @@ -794,37 +765,31 @@ } } - // Pruefen ob die Nachricht schon als queued im Chat steht - const existingQueued = get(messages).find((m) => m.queued && m.content === text); - if (existingQueued) { - // Queued-Markierung entfernen — Nachricht ist jetzt aktiv - messages.update((msgs) => - msgs.map((m) => m.id === existingQueued.id ? { ...m, queued: false } : m) - ); - // In DB speichern (wurde vorher nicht gespeichert da queued) - await saveMessageToDb({ ...existingQueued, queued: false }); - } else { - // Neue Nachricht hinzufuegen (normaler Send, nicht aus Queue) - const msgId = crypto.randomUUID(); - const msg: Message = { - id: msgId, - role: 'user', - content: text, - timestamp: new Date(), - }; - messages.update((msgs) => [...msgs, msg]); - // Sofort speichern (nicht auf Subscriber warten) - await saveMessageToDb(msg); - } + // User-Nachricht sofort im Chat anzeigen + in DB speichern + const msgId = crypto.randomUUID(); + const msg: Message = { + id: msgId, + role: 'user', + content: text, + timestamp: new Date(), + }; + messages.update((msgs) => [...msgs, msg]); + await saveMessageToDb(msg); $currentInput = ''; - $isProcessing = true; + + // isProcessing nur setzen wenn nicht schon aktiv + // (bei gepufferten Nachrichten laeuft Claude ja schon) + if (!$isProcessing) { + $isProcessing = true; + } try { await invoke('send_message', { message: text }); } catch (err) { console.error('Fehler beim Senden:', err); addMessage('system', `Fehler: ${err}`); + // Nur auf false setzen wenn keine weiteren Nachrichten pending $isProcessing = false; } } @@ -1239,18 +1204,11 @@ {liveTranscript} {/if} - {#if $messageQueue.length > 0} -
- 📬 - {$messageQueue.length} Nachricht{$messageQueue.length > 1 ? 'en' : ''} in der Queue - -
- {/if}