From 3165bc78616f7958b1aa8641ede1f19f3b15ae44 Mon Sep 17 00:00:00 2001 From: Nathan Walston Date: Tue, 18 Aug 2026 15:23:29 -0700 Subject: [PATCH 1/3] feat(studio): remove the Agent Evaluations list page and its nav ASTD-439. Delete AgentEvaluationsListRoute + test; remove the agentEvaluationsList route, its ROUTES path, the getAgentEvaluationsListRoute builder, the rail nav sub-entry, the 'agent-evaluations' assistant nav suggestion, and the dead agent_evaluations link template. Keep the per-job AgentEvaluationDetailRoute and repoint its breadcrumb off the deleted list route to the agent-detail Evaluations tab. Also keep the Agents rail group as a link to the agents list even when it has no sub-items (previously it only rendered when it had children, so removing the sole 'Agent Evaluations' sub-entry would have dropped the whole Agents entry from the rail when Monitor is disabled). Signed-off-by: Nathan Walston --- web/packages/studio/src/constants/routes.ts | 2 - .../WorkspaceLayout/WorkspaceSideNav.tsx | 4 +- .../AgentEvaluationDetailRoute.tsx | 21 +++++---- .../AgentEvaluationsListRoute.test.tsx | 31 ------------- .../AgentEvaluationsListRoute.tsx | 45 ------------------- .../agents/AgentEvaluationsRoute/index.ts | 1 - .../agents/AssistantChatRoute/artifacts.ts | 1 - .../studioUiNavigationSuggestions.test.ts | 7 --- .../studioUiNavigationSuggestions.ts | 14 ------ .../studio/src/routes/groups/agentRoutes.tsx | 22 +-------- web/packages/studio/src/routes/utils.ts | 4 -- 11 files changed, 15 insertions(+), 137 deletions(-) delete mode 100644 web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.test.tsx delete mode 100644 web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx diff --git a/web/packages/studio/src/constants/routes.ts b/web/packages/studio/src/constants/routes.ts index 24be38af1c..69e4386e25 100644 --- a/web/packages/studio/src/constants/routes.ts +++ b/web/packages/studio/src/constants/routes.ts @@ -131,8 +131,6 @@ export const ROUTES = { agentDetail: `/workspaces/:${P.workspace}/agents/:${P.agentName}`, agentDeploymentsList: `/workspaces/:${P.workspace}/agent-deployments`, agentDeploymentDetail: `/workspaces/:${P.workspace}/agent-deployments/:${P.agentDeploymentName}`, - /** Agent-evaluation jobs list (Phase 2 of the agent-eval UX). */ - agentEvaluationsList: `/workspaces/:${P.workspace}/agents/evaluations`, /** Detail view for a single agent-evaluation job. */ agentEvaluationDetail: `/workspaces/:${P.workspace}/agents/evaluations/:${P.agentEvalJobName}`, modelCompare: `/workspaces/:${P.workspace}/playground`, diff --git a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx index 7ff3df4ac5..3c5dc7843e 100644 --- a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx +++ b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx @@ -115,8 +115,8 @@ export const WorkspaceSideNav = ({ collapsed }: { collapsed?: boolean }) => { ]; const datasetSubItems = [...anonymizerNav, ...dataDesignerNav, ...safeSynthesizerNav]; - // Agents and Models link to their own entity list page; the chevron expands the rest. - const agentsHref = agentItems.length > 0 ? getAgentsListRoute(workspace) : undefined; + // Agents and Models link to their own entity list page; the chevron expands any sub-items. + const agentsHref = showAgents ? getAgentsListRoute(workspace) : undefined; const modelsHref = BASE_MODELS_ENABLED ? getWorkspaceBaseModelsRoute(workspace) : undefined; const componentItems = [ diff --git a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx index d85ac7e518..c6660bdb32 100644 --- a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx +++ b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx @@ -42,7 +42,7 @@ import { ROUTE_PARAMS } from '@studio/constants/routes'; import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; import { - getAgentEvaluationsListRoute, + getAgentEvaluationsTabRoute, getAgentsListRoute, getFilesetRoute, } from '@studio/routes/utils'; @@ -71,14 +71,6 @@ export const AgentEvaluationDetailRoute: FC = () => { const toast = useToast(); const queryClient = useQueryClient(); - useBreadcrumbs({ - items: [ - { slotLabel: 'Agents', href: getAgentsListRoute(workspace) }, - { slotLabel: 'Evaluations', href: getAgentEvaluationsListRoute(workspace) }, - { slotLabel: jobName }, - ], - }); - // Job + status — refetched while the job is non-terminal so the badge stays // live without forcing a page reload. const { data: job, isLoading: isLoadingJob } = useQuery({ @@ -88,6 +80,17 @@ export const AgentEvaluationDetailRoute: FC = () => { refetchInterval: (query) => (isTerminal(query.state.data?.status) ? false : 5_000), }); + const agentName = job ? agentNameForJob(job) : null; + useBreadcrumbs({ + items: [ + { slotLabel: 'Agents', href: getAgentsListRoute(workspace) }, + agentName + ? { slotLabel: 'Evaluations', href: getAgentEvaluationsTabRoute(workspace, agentName) } + : { slotLabel: 'Evaluations' }, + { slotLabel: jobName }, + ], + }); + const isJobTerminal = isTerminal(job?.status); const canCancelJob = !!job?.status && !isJobTerminal; const [cancelModalOpen, setCancelModalOpen] = useState(false); diff --git a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.test.tsx b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.test.tsx deleted file mode 100644 index d0413648ed..0000000000 --- a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { ROUTES } from '@studio/constants/routes'; -import { workspace1 } from '@studio/mocks/entity-store/projects'; -import { AgentEvaluationsListRoute } from '@studio/routes/agents/AgentEvaluationsRoute'; -import { getAgentEvaluationsListRoute } from '@studio/routes/utils'; -import { renderRoute, screen } from '@studio/tests/util/render'; - -const workspace = workspace1.workspace; - -const renderList = () => - renderRoute(, { - history: getAgentEvaluationsListRoute(workspace), - routes: [ - { path: ROUTES.workspace.agentEvaluationsList, element: }, - ], - }); - -describe('AgentEvaluationsListRoute', () => { - it('renders the page header and submit button', async () => { - renderList(); - expect(await screen.findByText('Agent Evaluations')).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Run Evaluation' })).toBeInTheDocument(); - }); - - it('shows the empty state when no eval jobs are returned (default mock)', async () => { - renderList(); - expect(await screen.findByText('No evaluation jobs yet')).toBeInTheDocument(); - }); -}); diff --git a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx deleted file mode 100644 index 00a0b87810..0000000000 --- a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle'; -import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core'; -import { AgentEvaluationsDataView } from '@studio/components/dataViews/AgentEvaluationsDataView'; -import { SubmitEvaluationModal } from '@studio/components/evaluation/SubmitEvaluationModal'; -import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath'; -import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs'; -import { getAgentsListRoute } from '@studio/routes/utils'; -import { useState, type FC } from 'react'; - -export const AgentEvaluationsListRoute: FC = () => { - const workspace = useWorkspaceFromPath(); - const [submitOpen, setSubmitOpen] = useState(false); - useBreadcrumbs({ - items: [ - { slotLabel: 'Agents', href: getAgentsListRoute(workspace) }, - { slotLabel: 'Evaluations' }, - ], - }); - - return ( - - - setSubmitOpen(true)}> - Run Evaluation - - } - /> - - - setSubmitOpen(false)} - workspace={workspace} - /> - - ); -}; diff --git a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/index.ts b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/index.ts index cde92d691d..ed8a936240 100644 --- a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/index.ts +++ b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/index.ts @@ -2,4 +2,3 @@ // SPDX-License-Identifier: Apache-2.0 export { AgentEvaluationDetailRoute } from '@studio/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute'; -export { AgentEvaluationsListRoute } from '@studio/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute'; diff --git a/web/packages/studio/src/routes/agents/AssistantChatRoute/artifacts.ts b/web/packages/studio/src/routes/agents/AssistantChatRoute/artifacts.ts index f938f81a86..ab4b0663ad 100644 --- a/web/packages/studio/src/routes/agents/AssistantChatRoute/artifacts.ts +++ b/web/packages/studio/src/routes/agents/AssistantChatRoute/artifacts.ts @@ -49,7 +49,6 @@ const STUDIO_LINK_PATH_TEMPLATES: Record = { agent_chat: '/workspaces/{workspace}/agents/{name}?tab=chat-playground', agent_deployments: '/workspaces/{workspace}/agents', agent_deployment: '/workspaces/{workspace}/agents/{name}', - agent_evaluations: '/workspaces/{workspace}/agents/evaluations', agent_evaluation: '/workspaces/{workspace}/agents/evaluations/{name}', agent_monitor: '/workspaces/{workspace}/agents/monitor', base_models: '/workspaces/{workspace}/base-models', diff --git a/web/packages/studio/src/routes/agents/AssistantChatRoute/studioUiNavigationSuggestions.test.ts b/web/packages/studio/src/routes/agents/AssistantChatRoute/studioUiNavigationSuggestions.test.ts index c95385abc6..e7f97ecad3 100644 --- a/web/packages/studio/src/routes/agents/AssistantChatRoute/studioUiNavigationSuggestions.test.ts +++ b/web/packages/studio/src/routes/agents/AssistantChatRoute/studioUiNavigationSuggestions.test.ts @@ -111,13 +111,6 @@ describe('getStudioUiNavigationSuggestion', () => { }); }); - it('prefers agent-specific evaluation routes over general model evaluations', () => { - expect(getStudioUiNavigationSuggestion('Evaluate an agent', workspace)).toMatchObject({ - id: 'agent-evaluations', - href: '/workspaces/default/agents/evaluations', - }); - }); - it('returns undefined when the matching feature is disabled', () => { mockFeatureFlags({ guardrailsEnabled: false }); diff --git a/web/packages/studio/src/routes/agents/AssistantChatRoute/studioUiNavigationSuggestions.ts b/web/packages/studio/src/routes/agents/AssistantChatRoute/studioUiNavigationSuggestions.ts index 265552294e..aa46de64c9 100644 --- a/web/packages/studio/src/routes/agents/AssistantChatRoute/studioUiNavigationSuggestions.ts +++ b/web/packages/studio/src/routes/agents/AssistantChatRoute/studioUiNavigationSuggestions.ts @@ -4,7 +4,6 @@ import { featureFlags } from '@studio/constants/featureFlags'; import type { FeatureFlags } from '@studio/constants/featureFlags/featureFlags'; import { - getAgentEvaluationsListRoute, getAgentMonitorRoute, getAgentsListRoute, getDataDesignerJobListRoute, @@ -59,19 +58,6 @@ const STUDIO_UI_DESTINATIONS: readonly StudioUiDestination[] = [ /\bsafety data\b/i, ], }, - { - id: 'agent-evaluations', - title: 'Open Agent Evaluations', - description: 'Studio has a UI for submitting and reviewing agent evaluation jobs.', - getHref: getAgentEvaluationsListRoute, - requiredFeatureFlags: ['agentsEnabled'], - patterns: [ - /\bagent (eval|evaluation|evaluations)\b/i, - /\bevaluat(e|ing|ion)s? (an? )?agent\b/i, - /\brun (an? )?(eval|evaluation) (for|on) (an? )?agent\b/i, - /\b(agent|agents).*\b(eval|evaluation|evaluations) jobs?\b/i, - ], - }, { id: 'agent-monitor', title: 'Open Agent Monitor', diff --git a/web/packages/studio/src/routes/groups/agentRoutes.tsx b/web/packages/studio/src/routes/groups/agentRoutes.tsx index 4ab7c8797d..972c01e623 100644 --- a/web/packages/studio/src/routes/groups/agentRoutes.tsx +++ b/web/packages/studio/src/routes/groups/agentRoutes.tsx @@ -7,10 +7,9 @@ import { ROUTES } from '@studio/constants/routes'; import { iconColorClass } from '@studio/routes/constants'; import { agentsRoutes, - getAgentEvaluationsListRoute, getAgentMonitorRoute, } from '@studio/routes/utils'; -import { Form, DatabaseCheck } from 'lucide-react'; +import { DatabaseCheck } from 'lucide-react'; import { lazy } from 'react'; import type { RouteObject } from 'react-router'; @@ -33,13 +32,6 @@ const AgentMonitorRoute = lazy(() => default: m.AgentMonitorRoute, })) ); -const AgentEvaluationsListRoute = - AGENTS_ENABLED && - lazy(() => - import('@studio/routes/agents/AgentEvaluationsRoute').then((m) => ({ - default: m.AgentEvaluationsListRoute, - })) - ); const AgentEvaluationDetailRoute = AGENTS_ENABLED && lazy(() => @@ -63,11 +55,6 @@ export const agentRoutes: RouteObject[] = agentsRoutes([ }, ] : []), - { - path: ROUTES.workspace.agentEvaluationsList, - element: AgentEvaluationsListRoute ? : null, - errorElement: , - }, { path: ROUTES.workspace.agentEvaluationDetail, element: AgentEvaluationDetailRoute ? : null, @@ -83,13 +70,6 @@ export const agentRoutes: RouteObject[] = agentsRoutes([ export const getAgentSideNavItems = (workspace: string) => AGENTS_ENABLED ? [ - { - id: 'agent-evaluations', - slotIcon:
, - // Qualified: the rail hoists this out of Agents, next to the model evaluations link. - slotLabel: 'Agent Evaluations', - href: getAgentEvaluationsListRoute(workspace), - }, ...(MONITOR_ENABLED ? [ { diff --git a/web/packages/studio/src/routes/utils.ts b/web/packages/studio/src/routes/utils.ts index b1bcd4384c..2a7a772f83 100644 --- a/web/packages/studio/src/routes/utils.ts +++ b/web/packages/studio/src/routes/utils.ts @@ -633,10 +633,6 @@ export const getAgentMonitorRoute = (workspace: string) => { return generatePath(ROUTES.workspace.agentMonitor, { workspace }); }; -export const getAgentEvaluationsListRoute = (workspace: string) => { - return generatePath(ROUTES.workspace.agentEvaluationsList, { workspace }); -}; - export const getAgentEvaluationDetailRoute = (workspace: string, agentEvalJobName: string) => { return generatePath(ROUTES.workspace.agentEvaluationDetail, { workspace, From c068d113d182d2e961148b695279d26e7547c5ed Mon Sep 17 00:00:00 2001 From: Nathan Walston Date: Wed, 19 Aug 2026 07:16:04 -0700 Subject: [PATCH 2/3] style(studio): prettier-format agentRoutes import after removal The removed getAgentEvaluationsListRoute import left a multiline import that Prettier collapses to one line. Fixes the Web format check. Signed-off-by: Nathan Walston --- web/packages/studio/src/routes/groups/agentRoutes.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web/packages/studio/src/routes/groups/agentRoutes.tsx b/web/packages/studio/src/routes/groups/agentRoutes.tsx index 972c01e623..2f03c6e1ac 100644 --- a/web/packages/studio/src/routes/groups/agentRoutes.tsx +++ b/web/packages/studio/src/routes/groups/agentRoutes.tsx @@ -5,10 +5,7 @@ import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel'; import { AGENTS_ENABLED, MONITOR_ENABLED } from '@studio/constants/environment'; import { ROUTES } from '@studio/constants/routes'; import { iconColorClass } from '@studio/routes/constants'; -import { - agentsRoutes, - getAgentMonitorRoute, -} from '@studio/routes/utils'; +import { agentsRoutes, getAgentMonitorRoute } from '@studio/routes/utils'; import { DatabaseCheck } from 'lucide-react'; import { lazy } from 'react'; import type { RouteObject } from 'react-router'; From 64b88e785887480fd95ab1d9d9dae6610c86569a Mon Sep 17 00:00:00 2001 From: Nathan Walston Date: Wed, 19 Aug 2026 08:07:48 -0700 Subject: [PATCH 3/3] fix(studio): update the eval-detail breadcrumb after the job loads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses CodeRabbit on #1391. useBreadcrumbs({ items }) runs its effect once on mount, so the 'Evaluations' crumb captured agentName===null and stayed non-clickable after the job query resolved. Set breadcrumbs from an effect keyed on [setBreadcrumbs, workspace, agentName, jobName] instead — the codebase's established pattern for dynamic breadcrumbs (see IntakeDetail Trace/Session detail views). Signed-off-by: Nathan Walston --- .../AgentEvaluationDetailRoute.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx index c6660bdb32..a156a223ce 100644 --- a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx +++ b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx @@ -49,7 +49,7 @@ import { import { useRequiredPathParams } from '@studio/util/hooks/useRequiredPathParams'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { CircleX, ClipboardList, FlaskConical, ScrollText } from 'lucide-react'; -import { type FC, useState } from 'react'; +import { type FC, useEffect, useState } from 'react'; import { Link } from 'react-router'; const TERMINAL_STATUSES = new Set([ @@ -80,16 +80,21 @@ export const AgentEvaluationDetailRoute: FC = () => { refetchInterval: (query) => (isTerminal(query.state.data?.status) ? false : 5_000), }); + // The "Evaluations" crumb links the agent's eval tab, but the agent name only arrives with + // the loaded job — so set breadcrumbs from an effect keyed on it (the useBreadcrumbs `items` + // param runs once on mount and would keep the crumb non-clickable after the job resolves). const agentName = job ? agentNameForJob(job) : null; - useBreadcrumbs({ - items: [ + const { setBreadcrumbs } = useBreadcrumbs(); + useEffect(() => { + setBreadcrumbs([ { slotLabel: 'Agents', href: getAgentsListRoute(workspace) }, agentName ? { slotLabel: 'Evaluations', href: getAgentEvaluationsTabRoute(workspace, agentName) } : { slotLabel: 'Evaluations' }, { slotLabel: jobName }, - ], - }); + ]); + return () => setBreadcrumbs([]); + }, [setBreadcrumbs, workspace, agentName, jobName]); const isJobTerminal = isTerminal(job?.status); const canCancelJob = !!job?.status && !isJobTerminal;