From a926d63abeb3bdf2de63dca6a88bddabf6aa5fda Mon Sep 17 00:00:00 2001 From: ciregenz Date: Fri, 31 Jul 2026 21:32:27 -0700 Subject: [PATCH] [eric] island: stop counting the workflow Edit Agent as one of the user's finished agents --- .../src/app/components/overlays/DynamicIsland.tsx | 8 ++++++-- .../hooks/lifecycle/useDashboardLifecycle.ts | 6 ++++-- frontend/src/shared/state/isUserLaunchedSession.ts | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 frontend/src/shared/state/isUserLaunchedSession.ts diff --git a/frontend/src/app/components/overlays/DynamicIsland.tsx b/frontend/src/app/components/overlays/DynamicIsland.tsx index 9922f92e..2e9bcc19 100644 --- a/frontend/src/app/components/overlays/DynamicIsland.tsx +++ b/frontend/src/app/components/overlays/DynamicIsland.tsx @@ -26,6 +26,7 @@ import { HistorySession, } from '@/shared/state/agentsSlice'; import { displaySessionName } from '@/shared/state/sessionDisplay'; +import { isUserLaunchedSession } from '@/shared/state/isUserLaunchedSession'; import { setPendingFocusAgentId } from '@/shared/state/tempStateSlice'; import ApprovalBar, { BatchApprovalBar, parseMcpToolName, useMcpToolMeta, getToolIcon } from '@/app/pages/AgentChat/shell/ApprovalBar'; import GlobalSearchPalette from '@/app/components/overlays/GlobalSearchPalette'; @@ -180,6 +181,7 @@ type DiSession = { name: string; status: string; dashboard_id?: string; + userLaunched: boolean; pending_approvals: AgentSession['pending_approvals']; }; @@ -207,6 +209,7 @@ const selectDynamicIslandSessions = createSelector( name: s.name, status: s.status, dashboard_id: s.dashboard_id, + userLaunched: isUserLaunchedSession(s), pending_approvals: s.pending_approvals, }; _diSessionCache.set(sid, next); @@ -277,10 +280,11 @@ const DynamicIsland: React.FC = () => { .map((id): TrackedAgent | null => { const session = sessions[id]; if (session && session.status !== 'draft') { + if (!session.userLaunched) return null; return { id, name: session.name, status: session.status, dashboardId: session.dashboard_id }; } const hist: HistorySession | undefined = history[id]; - if (hist) { + if (hist && isUserLaunchedSession(hist)) { return { id, name: hist.name, status: hist.status, dashboardId: hist.dashboard_id }; } return null; @@ -291,7 +295,7 @@ const DynamicIsland: React.FC = () => { for (const g of groups) { if (!trackedIdSet.has(g.sessionId)) { const session = sessions[g.sessionId]; - if (session && session.status !== 'draft') { + if (session && session.status !== 'draft' && session.userLaunched) { agents.push({ id: g.sessionId, name: session.name, status: session.status, dashboardId: session.dashboard_id }); } } diff --git a/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts b/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts index be7c4319..b5921356 100644 --- a/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts +++ b/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts @@ -23,6 +23,8 @@ import { } from '@/shared/state/dashboardLayoutSlice'; import { fetchOutputs, type Output } from '@/shared/state/outputsSlice'; import { generateDashboardName } from '@/shared/state/dashboardsSlice'; +import { isUserLaunchedSession } from '@/shared/state/isUserLaunchedSession'; +import { REVEAL_MIN_ZOOM } from '../../canvas/revealZoom'; import { fetchWorkflows, fetchAllRuns, fetchActiveRuns } from '@/shared/state/workflowsSlice'; import { fetchMissedRuns } from '@/shared/state/missedRunsSlice'; import { fetchProviderHealth } from '@/shared/state/subscriptionsSlice'; @@ -188,7 +190,7 @@ export function useDashboardLifecycle({ if (!layoutInitialized || hasFittedRef.current) return; if (pendingFocusAgentId) return; hasFittedRef.current = true; - const timer = setTimeout(() => canvasActions.fitToView(), 150); + const timer = setTimeout(() => canvasActions.fitToView(REVEAL_MIN_ZOOM), 150); return () => clearTimeout(timer); }, [isActive, layoutInitialized, canvasActions, pendingFocusAgentId]); @@ -303,7 +305,7 @@ export function useDashboardLifecycle({ useEffect(() => { if (!layoutInitialized) return; const dashboardSessionIds = Object.values(sessions) - .filter((s) => s.dashboard_id === dashboardId && !s.workflow_run_id && !s.workflow_edit_id && s.mode !== 'browser-agent' && s.mode !== 'invoked-agent' && s.mode !== 'sub-agent') + .filter((s) => s.dashboard_id === dashboardId && isUserLaunchedSession(s)) .map((s) => s.id); const liveIds = dashboardSessionIds.sort().join(','); if (liveIds === prevSessionIdsRef.current) return; diff --git a/frontend/src/shared/state/isUserLaunchedSession.ts b/frontend/src/shared/state/isUserLaunchedSession.ts new file mode 100644 index 00000000..20c595cf --- /dev/null +++ b/frontend/src/shared/state/isUserLaunchedSession.ts @@ -0,0 +1,14 @@ +// Plumbing chats the UI spins up for itself: a workflow card's Edit Agent, a workflow run, a browser +// or sub agent working for a parent. They are real sessions, they just aren't things the user started. +const PLUMBING_MODES: ReadonlySet = new Set(['browser-agent', 'invoked-agent', 'sub-agent']); + +export interface SessionOrigin { + mode: string; + workflow_run_id?: string | null; + workflow_edit_id?: string | null; +} + +/** True for a chat the user started themselves, which is the only kind that earns a card or a notification. */ +export function isUserLaunchedSession(session: SessionOrigin): boolean { + return !session.workflow_run_id && !session.workflow_edit_id && !PLUMBING_MODES.has(session.mode); +}