Skip to content
Closed
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
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"pydantic",
"relitigate",
"sandboxed",
"scoreable",
"sigils",
"Streamable",
"Syms",
Expand Down
25 changes: 19 additions & 6 deletions src/components/BenchResultPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { BenchJob, Episode, SUPPORTED_MODELS } from "@/lib/api";
import ExpandableErrorText from "@/components/ExpandableErrorText";
import { episodeStatusDetail } from "@/lib/runStatus";

const MODEL_LABELS: Record<string, string> = Object.fromEntries(
SUPPORTED_MODELS.map(({ id, label }) => [id, label])
Expand All @@ -13,6 +14,8 @@ const STATUS_STYLE: Record<string, string> = {
running: "text-leaf-deep",
queued: "text-ink-3",
timeout: "text-bad",
truncated: "text-warn",
cancelled: "text-warn",
};

function Metric({ label, value }: { label: string; value: string | number | null }) {
Expand Down Expand Up @@ -44,13 +47,23 @@ export function TestBenchResult({ episode }: TestBenchResultProps) {
<Metric label="steps" value={episode.steps} />
<Metric label="status" value={episode.status} />
</div>
{episode.status === "failed" && episode.terminal_info?.error != null && (
{(episode.status === "failed" ||
episode.status === "timeout" ||
episode.status === "truncated" ||
episode.status === "cancelled") && (
<div className="mt-3">
<p className="eyebrow text-bad mb-1">error</p>
<ExpandableErrorText
className="text-xs"
text={String(episode.terminal_info.error)}
/>
{episodeStatusDetail(episode) && (
<p className="text-xs text-ink-2 mb-2">{episodeStatusDetail(episode)}</p>
)}
{episode.terminal_info?.error != null && (
<>
<p className="eyebrow text-bad mb-1">error</p>
<ExpandableErrorText
className="text-xs"
text={String(episode.terminal_info.error)}
/>
</>
)}
</div>
)}
</div>
Expand Down
21 changes: 17 additions & 4 deletions src/components/DeveloperDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
testBench,
SUPPORTED_MODELS,
} from "@/lib/api";
import { reasonLabel } from "@/lib/runStatus";
import BindingVowChip from "@/components/BindingVowChip";
import { Btn } from "@/components/ds/Btn";
import { FullBenchResult, TestBenchResult } from "@/components/BenchResultPanel";
Expand Down Expand Up @@ -610,9 +611,16 @@ function EnvironmentCard({
<span className="text-ink num-tab truncate">
{run.config.agent_config.model ?? "unknown"}
</span>
<span className="text-ink-3 uppercase tracking-[0.14em] shrink-0">
{run.status}
</span>
<div className="text-right shrink-0">
<span className="text-ink-3 uppercase tracking-[0.14em] block">
{run.status}
</span>
{run.status_reason && (
<span className="text-[10px] text-ink-3 normal-case tracking-normal block mt-0.5 max-w-[12rem] truncate">
{reasonLabel(run.status_reason)}
</span>
)}
</div>
</li>
))}
</ul>
Expand Down Expand Up @@ -777,9 +785,14 @@ function DeveloperRunsPanel({
Domain →
</Link>
)}
<span className="text-[10px] uppercase tracking-[0.16em] text-ink-3">
<span className="text-[10px] uppercase tracking-[0.16em] text-ink-3 block">
{run.status}
</span>
{run.status_reason && (
<span className="text-[10px] text-ink-3 normal-case tracking-normal block mt-0.5 max-w-[14rem] truncate text-right">
{reasonLabel(run.status_reason)}
</span>
)}
</div>
</li>
))}
Expand Down
39 changes: 18 additions & 21 deletions src/components/ModelSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
} from "@/lib/api";
import { BenchAccessGate } from "@/components/BenchAccessGate";
import { Btn } from "@/components/ds/Btn";
import { RunStatusReason } from "@/components/RunStatusReason";
import { summarizeEpisodes } from "@/lib/runStatus";
import { useActiveTeam } from "@/hooks/useActiveTeam";
import { useBenchAuth } from "@/hooks/useBenchAuth";
import { getActiveTeamId } from "@/lib/benchAuth";
Expand All @@ -33,31 +35,12 @@ interface Props {

type RunView = { run: Run; episodes: Episode[] };

interface EpSummary {
total: number;
completed: number;
running: number;
pending: number;
failed: number;
timeout: number;
}

function summarizeEpisodes(episodes: Episode[]): EpSummary {
return {
total: episodes.length,
completed: episodes.filter((e) => e.status === "completed").length,
running: episodes.filter((e) => e.status === "running").length,
pending: episodes.filter((e) => e.status === "pending").length,
failed: episodes.filter((e) => e.status === "failed").length,
timeout: episodes.filter((e) => e.status === "timeout").length,
};
}

const RUN_TONE: Record<string, string> = {
completed: "text-ok",
failed: "text-bad",
timeout: "text-bad",
cancelled: "text-warn",
truncated: "text-warn",
running: "text-leaf-deep",
pending: "text-ink-3",
};
Expand Down Expand Up @@ -125,7 +108,7 @@ export default function ModelSelector({ domain, envId }: Props) {
listRunEpisodes(runId),
]);
return [runId, { run, episodes }] as const;
})
}),
);
if (!alive) return;
const next: Record<string, RunView> = {};
Expand Down Expand Up @@ -379,8 +362,22 @@ export default function ModelSelector({ domain, envId }: Props) {
{s.timeout}
</span>
)}
{s.truncated > 0 && (
<span className="text-warn">
<span className="uppercase tracking-[0.12em] text-[10px] mr-1">truncated</span>
{s.truncated}
</span>
)}
{s.cancelled > 0 && (
<span className="text-warn">
<span className="uppercase tracking-[0.12em] text-[10px] mr-1">cancelled</span>
{s.cancelled}
</span>
)}
</div>

<RunStatusReason run={run} />

{run.status === "completed" &&
Object.keys(run.scores ?? {}).length > 0 && (
<div className="text-xs num-tab text-ink bg-paper-2 border border-line rounded-[2px] px-3 py-2 overflow-x-auto">
Expand Down
52 changes: 35 additions & 17 deletions src/components/RecentRuns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useBenchAuth } from "@/hooks/useBenchAuth";
import { useActiveTeam } from "@/hooks/useActiveTeam";
import { ScopePill } from "@/components/ScopePill";
import ExpandableErrorText from "@/components/ExpandableErrorText";
import { EpisodeTerminalReason, RunStatusReason } from "@/components/RunStatusReason";
import { RunVisibilityButton } from "@/components/RunVisibilityButton";
import { benchAuthDisabled } from "@/lib/env";

Expand All @@ -42,6 +43,7 @@ const RUN_TONE: Record<string, string> = {
failed: "text-bad",
timeout: "text-bad",
cancelled: "text-warn",
truncated: "text-warn",
running: "text-leaf-deep",
pending: "text-ink-3",
};
Expand All @@ -51,6 +53,7 @@ const EP_DOT: Record<string, string> = {
failed: "bg-bad",
timeout: "bg-bad",
cancelled: "bg-warn",
truncated: "bg-warn",
running: "bg-leaf-deep animate-pulse",
pending: "bg-ink-3",
};
Expand Down Expand Up @@ -285,7 +288,10 @@ export default function RecentRuns({
const episodes = episodesByRunId[run.id];
const episodesLoading = loadingEpisodes.has(run.id);
const completedEps = episodes?.filter((e) => e.status === "completed") ?? [];
const failedEps = episodes?.filter((e) => e.status === "failed") ?? [];
const failedEps =
episodes?.filter((e) =>
["failed", "timeout", "cancelled", "truncated"].includes(e.status),
) ?? [];
const totalReward = completedEps.reduce((s, e) => s + e.total_reward, 0);
const avgReward =
completedEps.length > 0 ? totalReward / completedEps.length : 0;
Expand Down Expand Up @@ -357,6 +363,10 @@ export default function RecentRuns({
</div>
</div>

{(run.status === "failed" || run.status === "cancelled") && (
<RunStatusReason run={run} className="mt-2 pl-5" />
)}

{run.status === "completed" &&
Object.keys(run.scores ?? {}).length > 0 && (
<div className="mt-2 pl-5 flex flex-wrap gap-x-4 gap-y-1">
Expand Down Expand Up @@ -415,13 +425,16 @@ export default function RecentRuns({
key={ep.id}
className="flex items-center justify-between text-xs bg-paper-2 border border-line rounded-[2px] px-3 py-2"
>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 min-w-0">
<span
className={`w-1.5 h-1.5 rounded-full ${EP_DOT[ep.status] ?? "bg-ink-3"}`}
className={`w-1.5 h-1.5 rounded-full shrink-0 ${EP_DOT[ep.status] ?? "bg-ink-3"}`}
/>
<span className="text-ink-2 num-tab">
{ep.id.slice(0, 8)}
</span>
<div className="min-w-0">
<span className="text-ink-2 num-tab block">
{ep.id.slice(0, 8)}
</span>
<EpisodeTerminalReason episode={ep} />
</div>
</div>
<div className="flex items-center gap-3 text-ink-2 num-tab">
<span>
Expand Down Expand Up @@ -460,17 +473,22 @@ export default function RecentRuns({
{!episodesLoading &&
episodes &&
failedEps.length > 0 &&
Boolean(failedEps[0].terminal_info?.error) && (
<div className="pl-5 mt-1">
<details className="text-xs">
<summary className="text-bad cursor-pointer uppercase tracking-[0.16em] text-[10px] font-medium">
error details
</summary>
<ExpandableErrorText
className="mt-2"
text={String(failedEps[0].terminal_info.error)}
/>
</details>
failedEps.some((ep) => ep.terminal_info?.error) && (
<div className="pl-5 mt-1 space-y-2">
{failedEps
.filter((ep) => ep.terminal_info?.error)
.slice(0, 3)
.map((ep) => (
<details key={ep.id} className="text-xs">
<summary className="text-bad cursor-pointer uppercase tracking-[0.16em] text-[10px] font-medium">
{ep.status} · {ep.id.slice(0, 8)}
</summary>
<ExpandableErrorText
className="mt-2"
text={String(ep.terminal_info.error)}
/>
</details>
))}
</div>
)}
</div>
Expand Down
105 changes: 105 additions & 0 deletions src/components/RunStatusReason.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"use client";

import type { Episode, Run } from "@/lib/api";
import {
episodeStatusDetail,
formatEpisodeOutcomes,
formatReasonList,
reasonLabel,
runNeedsStatusDetail,
} from "@/lib/runStatus";

interface Props {
run: Run;
className?: string;
}

/** Run-level terminal explanation from bench-api `status_reason` / `status_detail`. */
export function RunStatusReason({ run, className = "" }: Props) {
if (!runNeedsStatusDetail(run.status) && !run.status_reason) return null;

const detail = run.status_detail;
const outcomes =
formatEpisodeOutcomes(detail) ??
(run.completed_count != null || run.truncated_count != null
? [
run.completed_count ? `completed ${run.completed_count}` : null,
run.truncated_count ? `truncated ${run.truncated_count}` : null,
run.failed_count ? `failed ${run.failed_count}` : null,
run.cancelled_count ? `cancelled ${run.cancelled_count}` : null,
]
.filter(Boolean)
.join(", ")
: null);

const failureReasons =
formatReasonList(detail, "failure_reasons") ??
(run.failure_reasons?.length
? run.failure_reasons.map((r) => reasonLabel(r)).join(", ")
: null);
const truncationReasons =
formatReasonList(detail, "truncation_reasons") ??
(run.truncation_reasons?.length
? run.truncation_reasons.map((r) => reasonLabel(r)).join(", ")
: null);

if (!run.status_reason && !outcomes && !failureReasons && !truncationReasons) {
return null;
}

return (
<div className={`text-xs text-ink-2 space-y-1 ${className}`}>
{run.status_reason && (
<p>
<span className="text-ink-3 uppercase tracking-[0.12em] text-[10px] mr-1">
reason
</span>
<span className={run.status === "cancelled" ? "text-warn" : "text-bad"}>
{reasonLabel(run.status_reason)}
</span>
</p>
)}
{outcomes && (
<p className="num-tab">
<span className="text-ink-3 uppercase tracking-[0.12em] text-[10px] mr-1">
episodes
</span>
{outcomes}
</p>
)}
{truncationReasons && (
<p>
<span className="text-ink-3 uppercase tracking-[0.12em] text-[10px] mr-1">
truncated
</span>
{truncationReasons}
</p>
)}
{failureReasons && (
<p>
<span className="text-ink-3 uppercase tracking-[0.12em] text-[10px] mr-1">
failures
</span>
{failureReasons}
</p>
)}
{detail?.detail && (
<p className="num-tab text-ink-3 break-all">{String(detail.detail)}</p>
)}
</div>
);
}

interface EpisodeReasonProps {
episode: Episode;
className?: string;
}

export function EpisodeTerminalReason({ episode, className = "" }: EpisodeReasonProps) {
if (episode.status === "completed" || episode.status === "pending") return null;
const text = episodeStatusDetail(episode);
if (!text) return null;
return (
<p className={`text-[10px] text-ink-3 mt-0.5 ${className}`}>{text}</p>
);
}
Loading
Loading