From 03c1cb1c910c100cefc847f49d48216eab2e45a7 Mon Sep 17 00:00:00 2001 From: haikdc Date: Fri, 20 Mar 2026 12:25:49 -0700 Subject: [PATCH] [Haik]: ckpt, auto spawn sub agent feature done and toggleable via settings --- backend/apps/settings/models.py | 1 + .../src/app/pages/Dashboard/Dashboard.tsx | 97 +++++++++++++++++++ frontend/src/app/pages/Settings/Settings.tsx | 17 +++- frontend/src/shared/state/settingsSlice.ts | 2 + 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/backend/apps/settings/models.py b/backend/apps/settings/models.py index bdd41e0c..e2e3061f 100644 --- a/backend/apps/settings/models.py +++ b/backend/apps/settings/models.py @@ -23,4 +23,5 @@ class AppSettings(BaseModel): browser_homepage: str = "https://www.google.com" auto_select_mode_on_new_agent: bool = False expand_new_chats_in_dashboard: bool = False + auto_reveal_sub_agents: bool = True dev_mode: bool = False diff --git a/frontend/src/app/pages/Dashboard/Dashboard.tsx b/frontend/src/app/pages/Dashboard/Dashboard.tsx index f6dab14e..064e12b4 100644 --- a/frontend/src/app/pages/Dashboard/Dashboard.tsx +++ b/frontend/src/app/pages/Dashboard/Dashboard.tsx @@ -34,7 +34,9 @@ import { removeBrowserCard, pasteBrowserCard, placeCard, + removeCard, setGlowingAgentCard, + clearGlowingAgentCard, DEFAULT_CARD_W, DEFAULT_CARD_H, EXPANDED_CARD_MIN_H, @@ -97,6 +99,7 @@ const DashboardInner: React.FC = () => { const newAgentShortcut = useAppSelector((state) => state.settings.data.new_agent_shortcut); const browserHomepage = useAppSelector((state) => state.settings.data.browser_homepage); const expandNewChats = useAppSelector((state) => state.settings.data.expand_new_chats_in_dashboard); + const autoRevealSubAgents = useAppSelector((state) => state.settings.data.auto_reveal_sub_agents); const outputs = useAppSelector((state) => state.outputs.items); const glowingAgentCards = useAppSelector((state) => state.dashboardLayout.glowingAgentCards); const sessionList = Object.values(sessions); @@ -357,6 +360,100 @@ const DashboardInner: React.FC = () => { dispatch(reconcileSessions({ sessionIds: dashboardSessionIds, expandedSessionIds })); }, [sessions, layoutInitialized, dispatch, dashboardId, expandedSessionIds]); + // ---- Auto-reveal / collapse / unreveal sub-agent cards ---- + const autoRevealedRef = useRef(new Set()); + const prevSubStatusRef = useRef>({}); + const prevParentStatusRef = useRef>({}); + + useEffect(() => { + if (!layoutInitialized || !autoRevealSubAgents) return; + + const subSessions = Object.values(sessions).filter( + (s) => (s.mode === 'sub-agent' || s.mode === 'invoked-agent') && s.parent_session_id, + ); + + // 1) Auto-reveal newly spawned sub-agents (skip already-terminal ones on load) + for (const sub of subSessions) { + if (autoRevealedRef.current.has(sub.id)) continue; + if (cards[sub.id]) { + autoRevealedRef.current.add(sub.id); + continue; + } + const parentCard = cards[sub.parent_session_id!]; + if (!parentCard) continue; + + const isTerminal = sub.status === 'completed' || sub.status === 'error' || sub.status === 'stopped'; + const parentSession = sessions[sub.parent_session_id!]; + const parentTerminal = parentSession && + (parentSession.status === 'completed' || parentSession.status === 'error' || parentSession.status === 'stopped'); + if (isTerminal && parentTerminal) { + autoRevealedRef.current.add(sub.id); + continue; + } + + autoRevealedRef.current.add(sub.id); + + const targetX = parentCard.x + parentCard.width + GRID_GAP * 12; + let targetY = parentCard.y; + const columnCards = Object.values(cards).filter( + (c) => Math.abs(c.x - targetX) < 50 && c.session_id !== sub.id, + ); + if (columnCards.length > 0) { + const lowestBottom = Math.max( + ...columnCards.map((c) => c.y + Math.max(EXPANDED_CARD_MIN_H, c.height)), + ); + targetY = lowestBottom + GRID_GAP; + } + + dispatch(placeCard({ sessionId: sub.id, x: targetX, y: targetY, width: DEFAULT_CARD_W, height: DEFAULT_CARD_H })); + dispatch(expandSession(sub.id)); + const label = sub.mode === 'sub-agent' ? 'Create Agent' : 'Invoke Agent'; + dispatch(setGlowingAgentCard({ sessionId: sub.id, sourceId: sub.parent_session_id!, label })); + + if (sub.status === 'completed' || sub.status === 'error' || sub.status === 'stopped') { + const subId = sub.id; + setTimeout(() => dispatch(collapseSession(subId)), 2000); + } + } + + // 2) Auto-collapse sub-agents when they complete + const TERMINAL = new Set(['completed', 'error', 'stopped']); + for (const sub of subSessions) { + const prev = prevSubStatusRef.current[sub.id]; + if (prev !== sub.status && TERMINAL.has(sub.status) && cards[sub.id]) { + dispatch(collapseSession(sub.id)); + } + } + const newSubStatuses: Record = {}; + for (const sub of subSessions) { newSubStatuses[sub.id] = sub.status; } + prevSubStatusRef.current = newSubStatuses; + + // 3) Unreveal all sub-agent cards when parent finishes output + const parentIds = new Set(subSessions.map((s) => s.parent_session_id!)); + for (const pid of parentIds) { + const parent = sessions[pid]; + if (!parent) continue; + const prev = prevParentStatusRef.current[pid]; + if (prev !== parent.status && TERMINAL.has(parent.status)) { + const children = subSessions.filter((s) => s.parent_session_id === pid); + for (const child of children) { + if (!cards[child.id]) continue; + dispatch(collapseSession(child.id)); + dispatch(removeCard(child.id)); + setTimeout(() => { + dispatch(clearGlowingAgentCard(child.id)); + }, 500); + } + } + } + const newParentStatuses: Record = {}; + for (const pid of parentIds) { + const parent = sessions[pid]; + if (parent) newParentStatuses[pid] = parent.status; + } + prevParentStatusRef.current = newParentStatuses; + }, [sessions, cards, layoutInitialized, autoRevealSubAgents, dispatch]); + const skipInitialSave = useRef(true); const saveTimerRef = useRef | null>(null); const pendingSaveRef = useRef[0] | null>(null); diff --git a/frontend/src/app/pages/Settings/Settings.tsx b/frontend/src/app/pages/Settings/Settings.tsx index 06acc65c..79f743bc 100644 --- a/frontend/src/app/pages/Settings/Settings.tsx +++ b/frontend/src/app/pages/Settings/Settings.tsx @@ -569,7 +569,7 @@ const Settings: React.FC = () => { /> - + Default agent spawn state in dashboard When enabled, new agents spawn expanded instead of collapsed. @@ -584,6 +584,21 @@ const Settings: React.FC = () => { /> + + + Auto-reveal sub-agents on dashboard + Automatically show sub-agent cards (from CreateAgent / InvokeAgent) tethered to their parent on the dashboard. + + setForm({ ...form, auto_reveal_sub_agents: e.target.checked })} + sx={{ + '& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary }, + '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary }, + }} + /> + + {/* ── Browser ── */} Browser diff --git a/frontend/src/shared/state/settingsSlice.ts b/frontend/src/shared/state/settingsSlice.ts index a216149f..1f5921cf 100644 --- a/frontend/src/shared/state/settingsSlice.ts +++ b/frontend/src/shared/state/settingsSlice.ts @@ -24,6 +24,7 @@ export interface AppSettings { browser_homepage: string; auto_select_mode_on_new_agent: boolean; expand_new_chats_in_dashboard: boolean; + auto_reveal_sub_agents: boolean; dev_mode: boolean; } @@ -55,6 +56,7 @@ const initialState: SettingsState = { browser_homepage: 'https://www.google.com', auto_select_mode_on_new_agent: false, expand_new_chats_in_dashboard: false, + auto_reveal_sub_agents: true, dev_mode: false, }, loading: false,