fix(app): Barge-In kontext-scoped — Hauptchat-Frage killt/blockiert Projekt-Arbeit nicht mehr
Bug: waehrend ARIA in einem Projekt arbeitet, im Hauptchat eine Frage stellen → sie wurde angezeigt aber nicht verarbeitet (erst wenn das Projekt fertig war). Proxy kann nachweislich parallel (2 claude-Subprozesse) — der Flaschenhals war die App: interruptAriaIfBusy wertete den GLOBALEN agentActivity aus und feuerte bei jedem Senden-waehrend-irgendein-Kontext-arbeitet einen kontext-uebergreifenden cancel_request. Fix: Busy-Status kontextgenau aus queueStatus (/projects/queue-status) statt global. Barge-In (Brain-Cancel) nur noch wenn GENAU der fokussierte Kontext arbeitet; TTS wird weiterhin immer gestoppt wenn ARIA spricht. cancel_request traegt jetzt die projectId (fuer spaeteres kontext-scoped Cancel in der Bridge). Damit laufen Hauptchat und Projekt(e) in der App parallel — passend zum Multi-Threading im Brain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -295,6 +295,9 @@ const ChatScreen: React.FC = () => {
|
|||||||
const [projectNameById, setProjectNameById] = useState<Record<string, string>>({});
|
const [projectNameById, setProjectNameById] = useState<Record<string, string>>({});
|
||||||
// Queue-Status pro Kontext — polled alle 2s, fuer Status-Dots im Drawer
|
// Queue-Status pro Kontext — polled alle 2s, fuer Status-Dots im Drawer
|
||||||
const [queueStatus, setQueueStatus] = useState<Record<string, { busy: boolean; queue_size: number }>>({});
|
const [queueStatus, setQueueStatus] = useState<Record<string, { busy: boolean; queue_size: number }>>({});
|
||||||
|
// Ref-Spiegel fuer Callbacks (interruptAriaIfBusy liest den aktuellen
|
||||||
|
// Busy-Status des fokussierten Kontexts ohne stale Closure).
|
||||||
|
const queueStatusRef = useRef<Record<string, { busy: boolean; queue_size: number }>>({});
|
||||||
const [searchIndex, setSearchIndex] = useState(0); // welcher Treffer aktiv ist
|
const [searchIndex, setSearchIndex] = useState(0); // welcher Treffer aktiv ist
|
||||||
const [pendingAttachments, setPendingAttachments] = useState<{file: any, isPhoto: boolean}[]>([]);
|
const [pendingAttachments, setPendingAttachments] = useState<{file: any, isPhoto: boolean}[]>([]);
|
||||||
const [agentActivity, setAgentActivity] = useState<{activity: string, tool: string}>({activity: 'idle', tool: ''});
|
const [agentActivity, setAgentActivity] = useState<{activity: string, tool: string}>({activity: 'idle', tool: ''});
|
||||||
@@ -517,6 +520,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
const s = await brainApi.getProjectQueueStatus();
|
const s = await brainApi.getProjectQueueStatus();
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
setQueueStatus(s.contexts || {});
|
setQueueStatus(s.contexts || {});
|
||||||
|
queueStatusRef.current = s.contexts || {};
|
||||||
} catch {}
|
} catch {}
|
||||||
};
|
};
|
||||||
poll();
|
poll();
|
||||||
@@ -1939,18 +1943,27 @@ const ChatScreen: React.FC = () => {
|
|||||||
// mach lieber X" sagen wie in einem echten Gespraech.
|
// mach lieber X" sagen wie in einem echten Gespraech.
|
||||||
const interruptAriaIfBusy = useCallback(() => {
|
const interruptAriaIfBusy = useCallback(() => {
|
||||||
const speaking = audioService.isPlayingAudio();
|
const speaking = audioService.isPlayingAudio();
|
||||||
const thinking = agentActivity.activity !== 'idle';
|
// Multi-Threading: NUR den fokussierten Kontext als "busy" werten — nicht
|
||||||
if (!speaking && !thinking) return false;
|
// global. Sonst bricht eine Nachricht im Hauptchat die parallele Arbeit in
|
||||||
console.log('[Chat] Barge-In: speaking=%s thinking=%s — interrupting ARIA',
|
// einem Projekt ab (bzw. wird faelschlich als Barge-In behandelt und die
|
||||||
speaking, thinking);
|
// eigene Anfrage geht unter). Der Busy-Status kommt kontextgenau aus
|
||||||
|
// /projects/queue-status (queueStatusRef). agentActivity ist global und
|
||||||
|
// taugt dafuer nicht.
|
||||||
|
const pid = focusedProjectIdRef.current || '';
|
||||||
|
const focusKey = pid || '__main__';
|
||||||
|
const focusBusy = !!queueStatusRef.current?.[focusKey]?.busy;
|
||||||
|
if (!speaking && !focusBusy) return false;
|
||||||
|
console.log('[Chat] Barge-In: speaking=%s focusBusy=%s (ctx=%s) — interrupting',
|
||||||
|
speaking, focusBusy, focusKey);
|
||||||
|
// TTS immer stoppen wenn ARIA gerade spricht — egal welcher Kontext.
|
||||||
if (speaking) audioService.haltAllPlayback('user spricht (barge-in)');
|
if (speaking) audioService.haltAllPlayback('user spricht (barge-in)');
|
||||||
if (thinking) {
|
// Brain-Arbeit nur abbrechen wenn GENAU dieser Kontext arbeitet.
|
||||||
setAgentActivity({ activity: 'idle', tool: '' });
|
if (focusBusy) {
|
||||||
clearStuckWatchdog();
|
clearStuckWatchdog();
|
||||||
rvs.send('cancel_request' as any, {});
|
rvs.send('cancel_request' as any, { projectId: pid });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}, [agentActivity]);
|
}, []);
|
||||||
|
|
||||||
// Manueller Aufnahme-Knopf (VoiceButton) — Start.
|
// Manueller Aufnahme-Knopf (VoiceButton) — Start.
|
||||||
// Streaming-Variante: PcmStreamRecorder + Whisper-ML-Endpointer ersetzen
|
// Streaming-Variante: PcmStreamRecorder + Whisper-ML-Endpointer ersetzen
|
||||||
|
|||||||
Reference in New Issue
Block a user