mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-12 20:57:42 +02:00
[eric] browser: only keep AGENT-driven browsers alive across dashboard switches; manual ones don't render off their home (no bleed, reload on return)
This commit is contained in:
@@ -1160,8 +1160,7 @@ const BrowserCard: React.FC<Props> = ({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: 'none',
|
||||
// A foreign (kept-alive) card lives on another dashboard: hide its surface the SAME proven way inactive tabs do (visibility:hidden reliably stops the guest compositing yet keeps the webContents + session alive), not just the off-screen card position, a heavy live page (Discord) can leave its OS-level webview surface painted at the old spot even after the card moves off-screen. Skip while an agent is driving it: a hidden guest may not paint frames for the agent's screenshots.
|
||||
visibility: ((keepAliveHidden && !agentActive) || tab.id !== activeTabId) ? 'hidden' : 'visible',
|
||||
visibility: tab.id === activeTabId ? 'visible' : 'hidden',
|
||||
zIndex: tab.id === activeTabId ? 1 : 0,
|
||||
// Only during select mode does the page go click-through, so the element picker can grab the whole card from anywhere instead of just the header (a live webview swallows host clicks). Off select mode = live for browsing.
|
||||
pointerEvents: isElementSelectMode ? 'none' : 'auto',
|
||||
|
||||
@@ -26,7 +26,7 @@ import { fetchWorkflows, fetchAllRuns, fetchActiveRuns } from '@/shared/state/wo
|
||||
import { fetchMissedRuns } from '@/shared/state/missedRunsSlice';
|
||||
import { dashboardWs } from '@/shared/ws/WebSocketManager';
|
||||
import { initBrowserCommandHandler } from '@/shared/browserCommandHandler';
|
||||
import { getKeepAliveBrowserIds } from '@/shared/browserFocus';
|
||||
import { isAgentDrivingBrowser } from '@/shared/isAgentDrivingBrowser';
|
||||
import { clearPendingBrowserUrl, clearPendingFocusAgentId } from '@/shared/state/tempStateSlice';
|
||||
import { API_BASE } from '@/shared/config';
|
||||
import type { CanvasActions } from '../interaction/useCanvasControls';
|
||||
@@ -99,7 +99,12 @@ export function useDashboardLifecycle({
|
||||
hasFittedRef.current = false;
|
||||
restoredExpandedRef.current = false;
|
||||
setOutputsRefetched(false);
|
||||
dispatch(resetLayout({ keepBrowserIds: getKeepAliveBrowserIds() }));
|
||||
// Only keep AGENT-driven browsers alive across the switch; a manual browser is dropped here (reloads when you return) so its kept-alive surface can't bleed onto the dashboard you land on.
|
||||
const st = store.getState();
|
||||
const agentLiveIds = Object.keys(st.dashboardLayout.browserCards).filter(
|
||||
(id) => isAgentDrivingBrowser(st.agents.sessions, id, st.dashboardLayout.browserCards[id]?.spawned_by),
|
||||
);
|
||||
dispatch(resetLayout({ keepBrowserIds: agentLiveIds }));
|
||||
// CRITICAL path: these populate the cards the user expects to see on first paint. Don't defer.
|
||||
dispatch(fetchSessions({ dashboardId }));
|
||||
dispatch(fetchLayout({ dashboardId }));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useAppSelector } from '@/shared/hooks';
|
||||
import { isAgentDrivingBrowser } from '@/shared/isAgentDrivingBrowser';
|
||||
|
||||
// All of the dashboard's Redux reads in one place. Keeps Dashboard.tsx a thin composition layer instead of a 25-line selector wall.
|
||||
export function useDashboardSelectors(dashboardId: string) {
|
||||
@@ -19,14 +20,14 @@ export function useDashboardSelectors(dashboardId: string) {
|
||||
}
|
||||
return out;
|
||||
}, [allBrowserCards, dashboardId]);
|
||||
// Keep-alive browser cards from OTHER dashboards still in state (resetLayout preserved them across the switch). Rendered mounted-but-hidden by the card layer so their webContents + sessionStorage survive; kept OUT of `browserCards` so save/bounds/keyboard-nav only ever see THIS dashboard's cards (no cross-dashboard leak).
|
||||
// Only an AGENT-driven browser from another dashboard stays mounted-but-hidden here so its run keeps going in the background; a MANUAL browser is deliberately NOT rendered off its own dashboard (it reloads on return) because a kept-alive heavy page bleeds its webview surface onto whatever dashboard you're viewing. Kept OUT of `browserCards` so save/bounds/keyboard-nav only ever see THIS dashboard's cards.
|
||||
const keepAliveBrowserCards = useMemo(() => {
|
||||
const out: typeof allBrowserCards = {};
|
||||
for (const [id, bc] of Object.entries(allBrowserCards)) {
|
||||
if (bc.dashboard_id && bc.dashboard_id !== dashboardId) out[id] = bc;
|
||||
if (bc.dashboard_id && bc.dashboard_id !== dashboardId && isAgentDrivingBrowser(sessions, id, bc.spawned_by)) out[id] = bc;
|
||||
}
|
||||
return out;
|
||||
}, [allBrowserCards, dashboardId]);
|
||||
}, [allBrowserCards, dashboardId, sessions]);
|
||||
const workflowCards = useAppSelector((state) => state.dashboardLayout.workflowCards);
|
||||
const workflowsHub = useAppSelector((state) => state.dashboardLayout.workflowsHub);
|
||||
const pendingFocusWorkflowId = useAppSelector((state) => state.dashboardLayout.pendingFocusWorkflowId);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { AgentSession } from '@/shared/state/agentsSlice';
|
||||
|
||||
const ACTIVE_STATUSES = new Set<AgentSession['status']>(['running', 'waiting_approval']);
|
||||
|
||||
// True while an agent is actively running against this browser, so its webContents must survive a dashboard switch (the run keeps going in the background and the agent reaches it over CDP). A MANUAL browser is false: it stops rendering the moment you leave its dashboard, so it can't bleed onto another, and it reloads when you come back.
|
||||
export function isAgentDrivingBrowser(
|
||||
sessions: Record<string, AgentSession>,
|
||||
browserId: string,
|
||||
spawnedBy?: string | null,
|
||||
): boolean {
|
||||
for (const s of Object.values(sessions)) {
|
||||
if (s.browser_id === browserId && ACTIVE_STATUSES.has(s.status)) return true;
|
||||
}
|
||||
if (spawnedBy) {
|
||||
const parent = sessions[spawnedBy];
|
||||
if (parent && ACTIVE_STATUSES.has(parent.status)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user