diff --git a/backend/apps/agents/core/models.py b/backend/apps/agents/core/models.py index fae0caae..6d8f588d 100644 --- a/backend/apps/agents/core/models.py +++ b/backend/apps/agents/core/models.py @@ -13,6 +13,8 @@ class AgentConfig(BaseModel): max_turns: Optional[int] = None target_directory: Optional[str] = None dashboard_id: Optional[str] = None + workflow_run_id: Optional[str] = None + workflow_edit_id: Optional[str] = None # App cards the user picked to edit. When exactly one resolves, launch binds the chat's cwd to that app instead of seeding a new "Untitled App". selected_app_output_ids: Optional[list[str]] = None @@ -110,6 +112,10 @@ class AgentSession(BaseModel): dashboard_id: Optional[str] = None browser_id: Optional[str] = None parent_session_id: Optional[str] = None + # Set when this session IS a workflow run's agent; the run renders in the Workflows monitor card, so the canvas suppresses the duplicate standalone agent card. + workflow_run_id: Optional[str] = None + # Set when this session IS a workflow's embedded edit/compose chat; it lives in the Workflows hub window, so the canvas suppresses its standalone card and docks its browser below the hub. + workflow_edit_id: Optional[str] = None workflow_test_state: Optional[Literal["running", "complete", "error"]] = None # Browser memory signals, drive the subtle "remembered/learned" card chip so the user feels the agent getting smarter without lifting a finger. memory_recalled: bool = False diff --git a/backend/apps/agents/manager/AgentLaunch.py b/backend/apps/agents/manager/AgentLaunch.py index 40ff612b..c8d2a54a 100644 --- a/backend/apps/agents/manager/AgentLaunch.py +++ b/backend/apps/agents/manager/AgentLaunch.py @@ -115,6 +115,8 @@ class AgentLaunch(AgentManagerProtocol): repo_url=repo_url, branch=branch_name, dashboard_id=config.dashboard_id, + workflow_run_id=config.workflow_run_id, + workflow_edit_id=config.workflow_edit_id, thinking_level=getattr(global_settings, "default_thinking_level", "auto"), ) apply_context_window(session, global_settings) diff --git a/backend/apps/workflows/executor.py b/backend/apps/workflows/executor.py index 81ea5088..357396ab 100644 --- a/backend/apps/workflows/executor.py +++ b/backend/apps/workflows/executor.py @@ -64,7 +64,7 @@ def _resolve_allowed_tools(wf: Workflow) -> Optional[list[str]]: return list(wf.actions.configured_sets) -def p_resolve_run_dashboard_id(wf: Workflow) -> Optional[str]: +def resolve_workflow_dashboard_id(wf: Workflow) -> Optional[str]: """Pick the dashboard this run's agent attaches to, so browser tools work like in chat. Browser cards render only on the dashboard the renderer is currently showing, so we @@ -262,7 +262,8 @@ async def execute( allowed_tools=resolved_allowed_tools if resolved_allowed_tools is not None else [ "Read", "Edit", "Write", "Bash", "Glob", "Grep", "AskUserQuestion", ], - dashboard_id=p_resolve_run_dashboard_id(wf), + dashboard_id=resolve_workflow_dashboard_id(wf), + workflow_run_id=run.id, ) session = await agent_manager.launch_agent(config) diff --git a/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts b/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts index edb867e5..b73b5af4 100644 --- a/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts +++ b/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts @@ -279,7 +279,7 @@ export function useDashboardLifecycle({ useEffect(() => { if (!layoutInitialized) return; const dashboardSessionIds = Object.values(sessions) - .filter((s) => s.dashboard_id === dashboardId && s.mode !== 'browser-agent' && s.mode !== 'invoked-agent' && s.mode !== 'sub-agent') + .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') .map((s) => s.id); const liveIds = dashboardSessionIds.sort().join(','); if (liveIds === prevSessionIdsRef.current) return; diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts index 3740ff74..4fca9126 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts @@ -49,6 +49,18 @@ export function useDashboardController(dashboardId: string, isActive: boolean) { !!workflowsMonitorId && s.workflows.active.some((a) => a.workflow_id === workflowsMonitorId)); const workflowsMonitorLabel = monitorIsLive ? 'Watching' : 'Viewing'; + // The session id of the run the monitor is showing, mirroring RunMonitor's pinned-or-latest pick, so its browser tether can anchor to the monitor card. + const workflowsMonitorRunId = useAppSelector((s) => s.dashboardLayout.workflowsMonitorRunId); + const monitorRuns = useAppSelector((s) => (workflowsMonitorId ? s.workflows.runs[workflowsMonitorId] : undefined)); + const allRuns = useAppSelector((s) => s.workflows.allRuns); + const monitorRunSessionId = useMemo(() => { + if (!workflowsMonitorId) return null; + const run = workflowsMonitorRunId + ? (monitorRuns || []).find((r) => r.id === workflowsMonitorRunId) || allRuns.find((r) => r.id === workflowsMonitorRunId) + : (monitorRuns && monitorRuns[0]) || allRuns.find((r) => r.workflow_id === workflowsMonitorId); + return run?.session_id || null; + }, [workflowsMonitorId, workflowsMonitorRunId, monitorRuns, allRuns]); + const contentBounds = useMemo( () => computeContentBounds(cards, viewCards, browserCards, workflowCards, workflowsHub), [cards, viewCards, browserCards, workflowCards, workflowsHub], @@ -295,6 +307,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) { workflowsHub, workflowsMonitorCard, workflowsMonitorLabel, + monitorRunSessionId, }); return { diff --git a/frontend/src/shared/state/agentsSlice.ts b/frontend/src/shared/state/agentsSlice.ts index 4d586354..14a6f30d 100644 --- a/frontend/src/shared/state/agentsSlice.ts +++ b/frontend/src/shared/state/agentsSlice.ts @@ -93,6 +93,10 @@ export interface AgentSession { dashboard_id?: string; browser_id?: string | null; parent_session_id?: string | null; + /** Set when this session IS a workflow run's agent; the run shows in the Workflows monitor, so it gets no standalone canvas card. */ + workflow_run_id?: string | null; + /** Set when this session IS a workflow's embedded edit/compose chat; it lives in the Workflows hub, so it gets no standalone card and its browser docks below the hub. */ + workflow_edit_id?: string | null; /** Browser memory signals that drive the subtle "Remembered"/"Learned" card chip. */ memory_recalled?: boolean; memory_learned?: boolean;