mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-22 09:34:53 +02:00
[eric] keep browser agent overlay alive between sub-tasks by gating fade/hide on parent session status, show Waiting… instead of false
Done between tool calls
This commit is contained in:
@@ -14,7 +14,7 @@ import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
|
||||
import { AgentSession, AgentMessage, stopAgent, handleApproval } from '@/shared/state/agentsSlice';
|
||||
import { useAppDispatch } from '@/shared/hooks';
|
||||
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
|
||||
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
|
||||
interface Props {
|
||||
@@ -69,8 +69,22 @@ const BrowserAgentOverlay: React.FC<Props> = ({ session, browserWidth, browserHe
|
||||
const fadeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
// Check if the parent agent session is still running. If so, the overlay
|
||||
// stays alive even when this browser-agent sub-task completes — the parent
|
||||
// may send another BrowserAgent call momentarily. Only treat the overlay
|
||||
// as "done" when both the browser-agent session AND the parent are terminal.
|
||||
const parentStatus = useAppSelector((state) => {
|
||||
if (!session.parent_session_id) return null;
|
||||
return state.agents.sessions[session.parent_session_id]?.status ?? null;
|
||||
});
|
||||
const parentStillActive = parentStatus === 'running' || parentStatus === 'waiting_approval';
|
||||
|
||||
const isRunning = session.status === 'running' || session.status === 'waiting_approval';
|
||||
const isDone = session.status === 'completed' || session.status === 'error' || session.status === 'stopped';
|
||||
const browserDone = session.status === 'completed' || session.status === 'error' || session.status === 'stopped';
|
||||
// Only truly "done" (fade + hide) when the parent is also finished.
|
||||
// While the parent is still active, the overlay stays visible in a
|
||||
// "waiting for next task" state between sub-tasks.
|
||||
const isDone = browserDone && !parentStillActive;
|
||||
|
||||
const intervention = session.pending_approvals?.find(
|
||||
(a) => a.tool_name === 'RequestHumanIntervention',
|
||||
@@ -201,7 +215,7 @@ const BrowserAgentOverlay: React.FC<Props> = ({ session, browserWidth, browserHe
|
||||
: <SmartToyOutlinedIcon sx={{ fontSize: 14, color: accentColor }} />
|
||||
}
|
||||
|
||||
{isRunning && !intervention && (
|
||||
{(isRunning || (browserDone && parentStillActive)) && !intervention && (
|
||||
<Box
|
||||
sx={{
|
||||
width: 6,
|
||||
@@ -238,7 +252,9 @@ const BrowserAgentOverlay: React.FC<Props> = ({ session, browserWidth, browserHe
|
||||
>
|
||||
{isDone
|
||||
? session.status === 'completed' ? 'Done' : session.status === 'error' ? 'Error' : 'Stopped'
|
||||
: intervention ? 'Needs Help' : 'Browser Agent'}
|
||||
: intervention ? 'Needs Help'
|
||||
: browserDone && parentStillActive ? 'Waiting…'
|
||||
: 'Browser Agent'}
|
||||
</Typography>
|
||||
|
||||
<Tooltip title={expanded ? 'Collapse' : 'Expand'} placement="top">
|
||||
|
||||
@@ -1384,7 +1384,24 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
|
||||
};
|
||||
}).filter(Boolean) as Array<{ key: string; path: string; labelX: number; labelY: number; label: string; fading: boolean }>;
|
||||
|
||||
const browserTethers = Object.entries(glowingBrowserCards).map(([browserId, { sourceId, fading, label }]) => {
|
||||
// Build browser tethers from TWO sources and merge:
|
||||
// 1. glowingBrowserCards — the short-lived "flash" when a browser is first assigned
|
||||
// 2. Active browser-agent sessions — persistent as long as the agent runs
|
||||
//
|
||||
// Source #2 is the fix for tethers disappearing when the parent session
|
||||
// completes a turn (which clears glowingBrowserCards even though the
|
||||
// browser agent is still working). Source #1 covers the initial moment
|
||||
// before the browser-agent session is fully created. Together they
|
||||
// ensure the arrow is always visible when it should be.
|
||||
|
||||
type Anchor = { x: number; y: number; side: 'left' | 'right' | 'top' | 'bottom' };
|
||||
|
||||
function browserTether(
|
||||
browserId: string,
|
||||
sourceId: string,
|
||||
fading: boolean,
|
||||
label: string,
|
||||
) {
|
||||
const src = cards[sourceId];
|
||||
const dst = browserCards[browserId];
|
||||
if (!src || !dst) return null;
|
||||
@@ -1405,7 +1422,6 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
|
||||
const srcCx = srcX + src.width / 2;
|
||||
const dstCx = dstX + dst.width / 2;
|
||||
|
||||
type Anchor = { x: number; y: number; side: 'left' | 'right' | 'top' | 'bottom' };
|
||||
const srcAnchors: Anchor[] = [
|
||||
{ x: srcX + src.width, y: srcY + srcH * 0.54, side: 'right' },
|
||||
{ x: srcX, y: srcY + srcH * 0.54, side: 'left' },
|
||||
@@ -1466,14 +1482,33 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
|
||||
path: pathD,
|
||||
labelX,
|
||||
labelY,
|
||||
label: label || '',
|
||||
label,
|
||||
fading,
|
||||
};
|
||||
}).filter(Boolean) as Array<{ key: string; path: string; labelX: number; labelY: number; label: string; fading: boolean }>;
|
||||
}
|
||||
|
||||
// Source 1: glow-based (covers the initial flash before browser-agent session exists)
|
||||
const glowTethers = new Map<string, ReturnType<typeof browserTether>>();
|
||||
for (const [browserId, { sourceId, fading, label }] of Object.entries(glowingBrowserCards)) {
|
||||
const t = browserTether(browserId, sourceId, fading, label || '');
|
||||
if (t) glowTethers.set(browserId, t);
|
||||
}
|
||||
|
||||
// Source 2: active browser-agent sessions (persistent — survives parent turn completion)
|
||||
for (const s of sessionList) {
|
||||
if (s.mode !== 'browser-agent') continue;
|
||||
if (s.status !== 'running' && s.status !== 'waiting_approval') continue;
|
||||
if (!s.browser_id || !s.parent_session_id) continue;
|
||||
if (glowTethers.has(s.browser_id)) continue; // glow already covers this one
|
||||
const t = browserTether(s.browser_id, s.parent_session_id, false, '');
|
||||
if (t) glowTethers.set(s.browser_id, t);
|
||||
}
|
||||
|
||||
const browserTethers = Array.from(glowTethers.values()).filter(Boolean) as Array<{ key: string; path: string; labelX: number; labelY: number; label: string; fading: boolean }>;
|
||||
|
||||
return [...agentTethers, ...browserTethers];
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [glowingAgentCards, glowingBrowserCards, cards, browserCards, expandedSessionIds, liveDragInfo, measuredHeightsTick]);
|
||||
}, [glowingAgentCards, glowingBrowserCards, cards, browserCards, expandedSessionIds, liveDragInfo, measuredHeightsTick, sessionList]);
|
||||
|
||||
const dotSize = Math.max(1, 1.5 * canvas.zoom);
|
||||
const dotSpacing = 24 * canvas.zoom;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user