+ {/* Stop All button - only shown when active */}
+ {state !== 'idle' && (
+
+ )}
void;
+ onStopAll: () => void;
onRename: () => void;
onShowInfo: () => void;
rightPanelOpen: boolean;
@@ -80,6 +81,7 @@ export function GroupChatPanel({
totalCost,
costIncomplete,
onSendMessage,
+ onStopAll,
onRename,
onShowInfo,
rightPanelOpen,
@@ -117,6 +119,8 @@ export function GroupChatPanel({
participantCount={groupChat.participants.length}
totalCost={totalCost}
costIncomplete={costIncomplete}
+ state={state}
+ onStopAll={onStopAll}
onRename={onRename}
onShowInfo={onShowInfo}
rightPanelOpen={rightPanelOpen}
diff --git a/src/renderer/components/GroupChatParticipants.tsx b/src/renderer/components/GroupChatParticipants.tsx
index 362db0fda8..27e9d12610 100644
--- a/src/renderer/components/GroupChatParticipants.tsx
+++ b/src/renderer/components/GroupChatParticipants.tsx
@@ -13,6 +13,7 @@ import { ParticipantCard } from './ParticipantCard';
import { formatShortcutKeys } from '../utils/shortcutFormatter';
import { buildParticipantColorMap } from '../utils/participantColors';
import { useResizablePanel } from '../hooks';
+import { useGroupChatStore } from '../stores/groupChatStore';
interface GroupChatParticipantsProps {
theme: Theme;
@@ -62,6 +63,8 @@ export function GroupChatParticipants({
side: 'right',
});
+ const participantLiveOutput = useGroupChatStore((s) => s.participantLiveOutput);
+
// Generate consistent colors for all participants (including "Moderator" for the moderator card)
const participantColors = useMemo(() => {
return buildParticipantColorMap(['Moderator', ...participants.map((p) => p.name)], theme);
@@ -164,10 +167,11 @@ export function GroupChatParticipants({
key={participant.sessionId}
theme={theme}
participant={participant}
- state={participantStates.get(participant.sessionId) || 'idle'}
+ state={participantStates.get(participant.name) || 'idle'}
color={participantColors[participant.name]}
groupChatId={groupChatId}
onContextReset={handleContextReset}
+ liveOutput={participantLiveOutput.get(participant.name)}
/>
))
)}
diff --git a/src/renderer/components/GroupChatRightPanel.tsx b/src/renderer/components/GroupChatRightPanel.tsx
index b8a2ee41c3..cbdf0b8406 100644
--- a/src/renderer/components/GroupChatRightPanel.tsx
+++ b/src/renderer/components/GroupChatRightPanel.tsx
@@ -20,6 +20,7 @@ import {
type ParticipantColorInfo,
} from '../utils/participantColors';
import { useResizablePanel } from '../hooks';
+import { useGroupChatStore } from '../stores/groupChatStore';
export type GroupChatRightTab = 'participants' | 'history';
@@ -80,6 +81,8 @@ export function GroupChatRightPanel({
onJumpToMessage,
onColorsComputed,
}: GroupChatRightPanelProps): JSX.Element | null {
+ const participantLiveOutput = useGroupChatStore((s) => s.participantLiveOutput);
+
// Color preferences state
const [colorPreferences, setColorPreferences] = useState>({});
const { panelRef, onResizeStart, transitionClass } = useResizablePanel({
@@ -322,6 +325,7 @@ export function GroupChatRightPanel({
color={participantColors[participant.name]}
groupChatId={groupChatId}
onContextReset={handleContextReset}
+ liveOutput={participantLiveOutput.get(`${groupChatId}:${participant.name}`)}
/>
);
})
diff --git a/src/renderer/components/ParticipantCard.tsx b/src/renderer/components/ParticipantCard.tsx
index 2aec2b916c..c31e4f2de1 100644
--- a/src/renderer/components/ParticipantCard.tsx
+++ b/src/renderer/components/ParticipantCard.tsx
@@ -5,8 +5,17 @@
* session ID, context usage, stats, and cost.
*/
-import { MessageSquare, Copy, Check, DollarSign, RotateCcw, Server } from 'lucide-react';
-import { useState, useCallback } from 'react';
+import {
+ MessageSquare,
+ Copy,
+ Check,
+ DollarSign,
+ RotateCcw,
+ Server,
+ Eye,
+ EyeOff,
+} from 'lucide-react';
+import { useState, useCallback, useEffect, useRef } from 'react';
import type { Theme, GroupChatParticipant, SessionState } from '../types';
import { getStatusColor } from '../utils/theme';
import { formatCost } from '../utils/formatters';
@@ -19,6 +28,7 @@ interface ParticipantCardProps {
color?: string;
groupChatId?: string;
onContextReset?: (participantName: string) => void;
+ liveOutput?: string;
}
/**
@@ -39,9 +49,19 @@ export function ParticipantCard({
color,
groupChatId,
onContextReset,
+ liveOutput,
}: ParticipantCardProps): JSX.Element {
const [copied, setCopied] = useState(false);
const [isResetting, setIsResetting] = useState(false);
+ const [peekOpen, setPeekOpen] = useState(false);
+ const peekRef = useRef(null);
+
+ // Auto-scroll peek output to bottom
+ useEffect(() => {
+ if (peekOpen && peekRef.current) {
+ peekRef.current.scrollTop = peekRef.current.scrollHeight;
+ }
+ }, [peekOpen, liveOutput]);
// Use agent's session ID (clean GUID) when available, otherwise show pending
const agentSessionId = participant.agentSessionId;
@@ -236,7 +256,41 @@ export function ParticipantCard({
Resetting...
)}
+ {/* Peek button - always visible */}
+
+
+ {/* Live output peek panel */}
+ {peekOpen && (
+