Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/remove-agents-last-run-column.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@truefoundry/trueforge-ui': patch
---

Remove the Last run column from the agents library page.
59 changes: 2 additions & 57 deletions packages/trueforge-ui/src/atoms/AgentLibraryUsage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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`,
Expand Down Expand Up @@ -238,34 +230,6 @@ export function AgentSpendCell({
);
}

export function AgentLastRunCell({
usage,
loading,
agentName,
}: {
usage: AgentLibraryUsageStats | undefined;
loading: boolean;
agentName: string;
}) {
if (loading) return <Skeleton className="h-4 w-16 rounded" />;
if (usage?.lastRunAt === undefined) {
return (
<span
className="text-text-secondary whitespace-nowrap text-sm"
aria-label={`Last run unavailable for ${agentName}`}
>
—
</span>
);
}
const label = formatLastRunLabel(usage.lastRunAt);
return (
<span className="text-text-secondary whitespace-nowrap text-sm" aria-label={`Last run for ${agentName}: ${label}`}>
{label}
</span>
);
}

function UsageField({ label, children }: { label: string; children: ReactNode }) {
return (
<div className="flex flex-col gap-0.5">
Expand All @@ -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 (
<UsageField label="Last run">
<AgentLastRunCell usage={usage} loading={loading} agentName={agentName} />
</UsageField>
);
}
export function AgentLibraryUsageCells({ usage, loading, agentName, showMetrics }: AgentLibraryUsageCellsProps) {
if (!showMetrics) return null;
return (
<div className="grid grid-cols-2 gap-3 text-sm">
<UsageField label={agentLibraryUsageLabels.sessions}>
Expand All @@ -310,11 +260,6 @@ export function AgentLibraryUsageCells({
<UsageField label={agentLibraryUsageLabels.daily}>
<AgentLibrarySparklineCell points={usage?.dailyPoints} loading={loading} agentName={agentName} />
</UsageField>
{showLastRun ? (
<UsageField label="Last run">
<AgentLastRunCell usage={usage} loading={loading} agentName={agentName} />
</UsageField>
) : null}
</div>
);
}
Expand Down
18 changes: 3 additions & 15 deletions packages/trueforge-ui/src/atoms/AgentsLibrary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -73,7 +72,6 @@ export type AgentLibraryRowProps = {
scheduleSummary?: AgentScheduleSummary | null;
usage?: AgentLibraryUsageStats;
showMetricsColumns?: boolean;
showLastRunColumn?: boolean;
onOpenSchedules?: () => void;
onCreateSchedule?: () => void;
onOpen?: () => void;
Expand Down Expand Up @@ -369,7 +367,6 @@ export function AgentLibraryRow({
scheduleSummary,
usage,
showMetricsColumns = false,
showLastRunColumn = false,
onOpenSchedules,
onCreateSchedule,
onOpen,
Expand All @@ -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 = (
<SlottedUsageCells
loading={usageLoading}
agentName={agent.name}
showMetrics={showMetricsColumns}
showLastRun={showLastRunColumn}
{...(usage !== undefined ? { usage } : {})}
/>
);
Expand Down Expand Up @@ -503,7 +499,6 @@ export function AgentLibraryRow({
) : null}
<TableCell className="w-px">
<div className="flex items-center justify-end gap-3">
{showLastRunColumn ? <AgentLastRunCell usage={usage} loading={usageLoading} agentName={agent.name} /> : null}
{tryButton}
{overflowMenu}
</div>
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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 } : {})}
Expand Down Expand Up @@ -870,9 +860,7 @@ export function AgentsLibrary({ onSelectAgent, headerStart }: AgentsLibraryProps
</>
) : null}
<TableHead className="w-px">
<span className={showLastRunColumn ? undefined : 'sr-only'}>
{showLastRunColumn ? 'Last run' : 'Actions'}
</span>
<span className="sr-only">Actions</span>
</TableHead>
</TableRow>
</TableHeader>
Expand Down
98 changes: 33 additions & 65 deletions packages/trueforge-ui/src/atoms/lib/useAgentLibraryUsageStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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<AgentLibraryUsageStats> {
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<string | null | undefined> =
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;
Expand All @@ -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({
Expand All @@ -142,19 +114,16 @@ export function useAgentLibraryUsageStats({
}): {
usageByAgentId: Map<string, AgentLibraryUsageStats>;
metricsEnabled: boolean;
lastRunEnabled: boolean;
} {
const metrics = useOptionalAgentMetricsServer();
const sessions = useOptionalAgentSessionsServer();
const metricsEnabled = metrics != null;
const lastRunEnabled = sessions != null;
const [usageByAgentId, setUsageByAgentId] = useState<Map<string, AgentLibraryUsageStats>>(() => new Map());
const agentIdsKey = agentIds.join('\0');
const loadedIdsKeyRef = useRef<string | null>(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;
Expand All @@ -177,7 +146,6 @@ export function useAgentLibraryUsageStats({
void loadUsageForAgent({
agentId,
metrics,
sessions,
metersStart,
metersEnd,
sparklineStart,
Expand All @@ -204,7 +172,7 @@ export function useAgentLibraryUsageStats({
return () => {
cancelled = true;
};
}, [agentIdsKey, metrics, refreshKey, sessions]);
}, [agentIdsKey, metrics, refreshKey]);

return { usageByAgentId, metricsEnabled, lastRunEnabled };
return { usageByAgentId, metricsEnabled };
}
Loading
Loading