diff --git a/.changeset/remove-agents-last-run-column.md b/.changeset/remove-agents-last-run-column.md new file mode 100644 index 000000000..41dafc3b6 --- /dev/null +++ b/.changeset/remove-agents-last-run-column.md @@ -0,0 +1,5 @@ +--- +'@truefoundry/trueforge-ui': patch +--- + +Remove the Last run column from the agents library page. diff --git a/packages/trueforge-ui/src/atoms/AgentLibraryUsage.tsx b/packages/trueforge-ui/src/atoms/AgentLibraryUsage.tsx index d7bf21593..c1ae10cbd 100644 --- a/packages/trueforge-ui/src/atoms/AgentLibraryUsage.tsx +++ b/packages/trueforge-ui/src/atoms/AgentLibraryUsage.tsx @@ -2,7 +2,6 @@ import { useState, type MouseEvent as ReactMouseEvent, type ReactNode } from 'react'; -import { formatRelativeShort } from './lib/threadListMeta.js'; import { AGENT_LIBRARY_METERS_WINDOW_DAYS, AGENT_LIBRARY_SPARKLINE_DAYS, @@ -30,13 +29,6 @@ function formatSparklineDay(iso: string): string { return sparklineDayFormatter.format(new Date(ms)); } -function formatLastRunLabel(iso: string | null): string { - if (iso == null) return 'Never'; - const ms = Date.parse(iso); - if (Number.isNaN(ms)) return '—'; - return `${formatRelativeShort(new Date(ms))} ago`; -} - export const agentLibraryUsageLabels = { sessions: `Sessions, ${String(AGENT_LIBRARY_METERS_WINDOW_DAYS)}d`, spend: `Spend, ${String(AGENT_LIBRARY_METERS_WINDOW_DAYS)}d`, @@ -238,34 +230,6 @@ export function AgentSpendCell({ ); } -export function AgentLastRunCell({ - usage, - loading, - agentName, -}: { - usage: AgentLibraryUsageStats | undefined; - loading: boolean; - agentName: string; -}) { - if (loading) return ; - if (usage?.lastRunAt === undefined) { - return ( - - — - - ); - } - const label = formatLastRunLabel(usage.lastRunAt); - return ( - - {label} - - ); -} - function UsageField({ label, children }: { label: string; children: ReactNode }) { return (
@@ -280,25 +244,11 @@ export type AgentLibraryUsageCellsProps = { loading: boolean; agentName: string; showMetrics: boolean; - showLastRun: boolean; }; /** Mobile card usage block. Desktop rows place the same cells in table columns. */ -export function AgentLibraryUsageCells({ - usage, - loading, - agentName, - showMetrics, - showLastRun, -}: AgentLibraryUsageCellsProps) { - if (!showMetrics && !showLastRun) return null; - if (!showMetrics) { - return ( - - - - ); - } +export function AgentLibraryUsageCells({ usage, loading, agentName, showMetrics }: AgentLibraryUsageCellsProps) { + if (!showMetrics) return null; return (
@@ -310,11 +260,6 @@ export function AgentLibraryUsageCells({ - {showLastRun ? ( - - - - ) : null}
); } diff --git a/packages/trueforge-ui/src/atoms/AgentsLibrary.tsx b/packages/trueforge-ui/src/atoms/AgentsLibrary.tsx index c68f09213..574d6d72b 100644 --- a/packages/trueforge-ui/src/atoms/AgentsLibrary.tsx +++ b/packages/trueforge-ui/src/atoms/AgentsLibrary.tsx @@ -16,7 +16,6 @@ import { useSlot } from '../theme/SlotsProvider.js'; import { readLibraryShareSearch, replaceLibraryShareSearch } from '../utils/libraryShareUrl.js'; import { replaceScheduleShareSearch } from '../utils/scheduleShareUrl.js'; import { - AgentLastRunCell, AgentLibrarySparklineCell, AgentSessionsCell, AgentSpendCell, @@ -73,7 +72,6 @@ export type AgentLibraryRowProps = { scheduleSummary?: AgentScheduleSummary | null; usage?: AgentLibraryUsageStats; showMetricsColumns?: boolean; - showLastRunColumn?: boolean; onOpenSchedules?: () => void; onCreateSchedule?: () => void; onOpen?: () => void; @@ -369,7 +367,6 @@ export function AgentLibraryRow({ scheduleSummary, usage, showMetricsColumns = false, - showLastRunColumn = false, onOpenSchedules, onCreateSchedule, onOpen, @@ -392,13 +389,12 @@ export function AgentLibraryRow({ const storedDescription = agent.description?.trim() || null; // Create falls back to name when description is missing; don't echo it under the title. const description = storedDescription != null && storedDescription !== agent.name ? storedDescription : null; - const usageLoading = (showMetricsColumns || showLastRunColumn) && usage == null; + const usageLoading = showMetricsColumns && usage == null; const usageCells = ( ); @@ -503,7 +499,6 @@ export function AgentLibraryRow({ ) : null}
- {showLastRunColumn ? : null} {tryButton} {overflowMenu}
@@ -628,11 +623,7 @@ export function AgentsLibrary({ onSelectAgent, headerStart }: AgentsLibraryProps resourceIds: permissionAgentIds, }); - const { - usageByAgentId, - metricsEnabled: showMetricsColumns, - lastRunEnabled: showLastRunColumn, - } = useAgentLibraryUsageStats({ + const { usageByAgentId, metricsEnabled: showMetricsColumns } = useAgentLibraryUsageStats({ agentIds: permissionAgentIds, refreshKey: agentsListEpoch, }); @@ -721,7 +712,6 @@ export function AgentsLibrary({ onSelectAgent, headerStart }: AgentsLibraryProps canDeleteAgent={allows(id, 'DELETE')} canManageSchedules={canOpenAgentSchedules} showMetricsColumns={showMetricsColumns} - showLastRunColumn={showLastRunColumn} variant={variant} {...(usage !== undefined ? { usage } : {})} {...(summary !== undefined ? { scheduleSummary: summary } : {})} @@ -870,9 +860,7 @@ export function AgentsLibrary({ onSelectAgent, headerStart }: AgentsLibraryProps ) : null} - - {showLastRunColumn ? 'Last run' : 'Actions'} - + Actions diff --git a/packages/trueforge-ui/src/atoms/lib/useAgentLibraryUsageStats.ts b/packages/trueforge-ui/src/atoms/lib/useAgentLibraryUsageStats.ts index 7790e0a49..14313ce56 100644 --- a/packages/trueforge-ui/src/atoms/lib/useAgentLibraryUsageStats.ts +++ b/packages/trueforge-ui/src/atoms/lib/useAgentLibraryUsageStats.ts @@ -2,13 +2,8 @@ import { useEffect, useRef, useState } from 'react'; -import { useOptionalAgentMetricsServer, useOptionalAgentSessionsServer } from '../../server/ServerContext.js'; -import type { - AgentMetricMeter, - AgentMetricPoint, - AgentMetricsServer, - AgentSessionsServer, -} from '../../server/types.js'; +import { useOptionalAgentMetricsServer } from '../../server/ServerContext.js'; +import type { AgentMetricMeter, AgentMetricPoint, AgentMetricsServer } from '../../server/types.js'; import { DEFAULT_SESSION_TIME_WINDOW_MS } from '../../utils/sessionShareUrl.js'; const MS_PER_DAY = 24 * 60 * 60 * 1000; @@ -29,11 +24,6 @@ export type AgentLibraryUsageStats = { totalCostUsd: number | null; costPerSessionUsd: number | null; dailyPoints: AgentLibraryDailyPoint[] | null; - /** - * Present only after `listSessions` succeeds. `null` means the agent has no - * sessions; omitted when the fetch failed (distinct from "never ran"). - */ - lastRunAt?: string | null; }; function meterValue(meters: readonly AgentMetricMeter[], name: string): number | null { @@ -61,59 +51,42 @@ function unavailableUsage(): AgentLibraryUsageStats { async function loadUsageForAgent({ agentId, metrics, - sessions, metersStart, metersEnd, sparklineStart, sparklineEnd, }: { agentId: string; - metrics: AgentMetricsServer | null; - sessions: AgentSessionsServer | null; + metrics: AgentMetricsServer; metersStart: string; metersEnd: string; sparklineStart: string; sparklineEnd: string; }): Promise { - const metersPromise = - metrics == null - ? Promise.resolve({ ok: false as const }) - : metrics - .getMeters({ - agentId, - startTimestamp: metersStart, - endTimestamp: metersEnd, - }) - .then( - meters => ({ ok: true as const, meters }), - () => ({ ok: false as const }), - ); - - const chartPromise = - metrics == null - ? Promise.resolve({ ok: false as const }) - : metrics - .getChartData({ - agentId, - startTimestamp: sparklineStart, - endTimestamp: sparklineEnd, - chartName: CHART_NAME, - }) - .then( - data => ({ ok: true as const, data }), - () => ({ ok: false as const }), - ); - - // `undefined` = sessions server absent or the call failed; `null` = no sessions. - const lastRunPromise: Promise = - sessions == null - ? Promise.resolve(undefined) - : sessions.listSessions({ agentId, limit: 1, order: 'desc' }).then( - page => page.data[0]?.lastActivityAt ?? null, - () => undefined, - ); - - const [metersResult, chartResult, lastRunAt] = await Promise.all([metersPromise, chartPromise, lastRunPromise]); + const metersPromise = metrics + .getMeters({ + agentId, + startTimestamp: metersStart, + endTimestamp: metersEnd, + }) + .then( + meters => ({ ok: true as const, meters }), + () => ({ ok: false as const }), + ); + + const chartPromise = metrics + .getChartData({ + agentId, + startTimestamp: sparklineStart, + endTimestamp: sparklineEnd, + chartName: CHART_NAME, + }) + .then( + data => ({ ok: true as const, data }), + () => ({ ok: false as const }), + ); + + const [metersResult, chartResult] = await Promise.all([metersPromise, chartPromise]); const meters = metersResult.ok ? metersResult.meters : []; const dailyPoints = chartResult.ok ? chartPoints(chartResult.data.graphs[0]?.graphLines[0]?.values) : null; @@ -123,14 +96,13 @@ async function loadUsageForAgent({ totalCostUsd: metersResult.ok ? meterValue(meters, 'total_cost_in_usd') : null, costPerSessionUsd: metersResult.ok ? meterValue(meters, 'cost_per_session_in_usd') : null, dailyPoints, - ...(lastRunAt === undefined ? {} : { lastRunAt }), }; } /** - * Loads per-agent spend/session meters, a 14d cost sparkline, and last-run - * for the visible library page. Each agent paints as its own requests finish. - * ponytail: three requests per agent (N+1). Replace with one batched metrics + * Loads per-agent spend/session meters and a 14d cost sparkline for the + * visible library page. Each agent paints as its own requests finish. + * ponytail: two requests per agent (N+1). Replace with one batched metrics * call if the library page grows past a few dozen rows. */ export function useAgentLibraryUsageStats({ @@ -142,19 +114,16 @@ export function useAgentLibraryUsageStats({ }): { usageByAgentId: Map; metricsEnabled: boolean; - lastRunEnabled: boolean; } { const metrics = useOptionalAgentMetricsServer(); - const sessions = useOptionalAgentSessionsServer(); const metricsEnabled = metrics != null; - const lastRunEnabled = sessions != null; const [usageByAgentId, setUsageByAgentId] = useState>(() => new Map()); const agentIdsKey = agentIds.join('\0'); const loadedIdsKeyRef = useRef(null); useEffect(() => { const ids = agentIdsKey.length === 0 ? [] : agentIdsKey.split('\0'); - if (ids.length === 0 || (metrics == null && sessions == null)) { + if (ids.length === 0 || metrics == null) { loadedIdsKeyRef.current = agentIdsKey; setUsageByAgentId(prev => (prev.size === 0 ? prev : new Map())); return; @@ -177,7 +146,6 @@ export function useAgentLibraryUsageStats({ void loadUsageForAgent({ agentId, metrics, - sessions, metersStart, metersEnd, sparklineStart, @@ -204,7 +172,7 @@ export function useAgentLibraryUsageStats({ return () => { cancelled = true; }; - }, [agentIdsKey, metrics, refreshKey, sessions]); + }, [agentIdsKey, metrics, refreshKey]); - return { usageByAgentId, metricsEnabled, lastRunEnabled }; + return { usageByAgentId, metricsEnabled }; } diff --git a/packages/trueforge-ui/test/atoms/AgentsLibrary.test.tsx b/packages/trueforge-ui/test/atoms/AgentsLibrary.test.tsx index c025fcb82..9c3645d19 100644 --- a/packages/trueforge-ui/test/atoms/AgentsLibrary.test.tsx +++ b/packages/trueforge-ui/test/atoms/AgentsLibrary.test.tsx @@ -1003,12 +1003,11 @@ describe('AgentsLibraryButton', () => { expect(await screen.findByRole('columnheader', { name: 'Sessions, 30d' })).toBeInTheDocument(); expect(screen.getByRole('columnheader', { name: 'Spend, 30d' })).toBeInTheDocument(); expect(screen.getByRole('columnheader', { name: 'Daily, 14d' })).toBeInTheDocument(); - expect(screen.getByRole('columnheader', { name: 'Last run' })).toBeInTheDocument(); + expect(screen.queryByRole('columnheader', { name: 'Last run' })).not.toBeInTheDocument(); await waitFor(() => { expect(getMeters).toHaveBeenCalled(); expect(getChartData).toHaveBeenCalled(); - expect(listSessions).toHaveBeenCalledWith({ agentId: 'metrics-agent', limit: 1, order: 'desc' }); }); expect(await screen.findByLabelText('12 sessions')).toBeInTheDocument(); @@ -1024,37 +1023,6 @@ describe('AgentsLibraryButton', () => { }).format(new Date('2026-10-01T00:00:00.000Z')); expect(await screen.findByRole('tooltip')).toHaveTextContent(utcDay); expect(screen.getByRole('tooltip')).toHaveTextContent('$0.10'); - expect(screen.getByLabelText(/Last run for metrics-agent:/)).toBeInTheDocument(); - }); - - it('shows Never when the agent has no sessions', async () => { - const server = createMockAgentUIServer({ - searchAgents: vi.fn(async () => ({ - data: [{ name: 'metrics-agent', agentId: 'metrics-agent' }], - })), - sessions: createMockAgentSessionsServer({ - listSessions: vi.fn(async () => ({ data: [] })), - }), - }); - renderLibrary(, { server }); - fireEvent.click(screen.getByRole('button', { name: 'Open library' })); - expect(await screen.findByLabelText('Last run for metrics-agent: Never')).toBeInTheDocument(); - }); - - it('shows an em dash for last run when the session list fails', async () => { - const server = createMockAgentUIServer({ - searchAgents: vi.fn(async () => ({ - data: [{ name: 'metrics-agent', agentId: 'metrics-agent' }], - })), - sessions: createMockAgentSessionsServer({ - listSessions: vi.fn(async () => { - throw new Error('sessions down'); - }), - }), - }); - renderLibrary(, { server }); - fireEvent.click(screen.getByRole('button', { name: 'Open library' })); - expect(await screen.findByLabelText('Last run unavailable for metrics-agent')).toHaveTextContent('—'); - expect(screen.queryByText('Never')).not.toBeInTheDocument(); + expect(listSessions).not.toHaveBeenCalledWith(expect.objectContaining({ agentId: 'metrics-agent', limit: 1 })); }); });