@post-print/agent-test 2.0.4 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import * as stylex from "@stylexjs/stylex";
3
3
  import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { createRootRoute, createRoute, createRouter, Link, Outlet, useNavigate, } from "@tanstack/react-router";
5
- import { createContext, useContext, useState } from "react";
5
+ import { createContext, useContext, useId, useState, } from "react";
6
6
  import Markdown from "react-markdown";
7
7
  import { viewerApi } from "./generated-api.js";
8
8
  import { assertionComparison, conversationPlaceholder, preferredExecutionId, stripAnsi, } from "./presentation.js";
@@ -646,6 +646,11 @@ const styles = stylex.create({
646
646
  },
647
647
  metricValue: { color: "var(--text)", fontSize: "0.77rem", fontWeight: 700 },
648
648
  conversation: {
649
+ display: "grid",
650
+ gap: "0.55rem",
651
+ padding: "0.2rem 0",
652
+ },
653
+ conversationSurface: {
649
654
  display: "grid",
650
655
  gap: "0.55rem",
651
656
  padding: "0.75rem",
@@ -662,7 +667,7 @@ const styles = stylex.create({
662
667
  conversationHeader: {
663
668
  display: "flex",
664
669
  alignItems: "center",
665
- justifyContent: "space-between",
670
+ justifyContent: "flex-start",
666
671
  gap: "0.75rem",
667
672
  },
668
673
  runTabs: { display: "inline-flex", alignItems: "center", gap: "0.12rem" },
@@ -678,34 +683,49 @@ const styles = stylex.create({
678
683
  cursor: "pointer",
679
684
  },
680
685
  runTabActive: { backgroundColor: "var(--accent-soft)", color: "var(--accent)" },
681
- conversationTabs: { display: "flex", flexWrap: "wrap", gap: "0.25rem" },
686
+ conversationTabs: {
687
+ display: "flex",
688
+ flexWrap: "wrap",
689
+ gap: "0.9rem",
690
+ padding: "0 0.15rem",
691
+ borderBottomWidth: 1,
692
+ borderBottomStyle: "solid",
693
+ borderBottomColor: "var(--border)",
694
+ },
682
695
  conversationTab: {
683
696
  display: "inline-flex",
684
697
  alignItems: "center",
685
- gap: "0.2rem",
698
+ gap: "0.3rem",
686
699
  boxSizing: "border-box",
687
- height: "1.1rem",
688
- minHeight: 0,
700
+ height: "1.85rem",
701
+ minHeight: "1.85rem",
689
702
  lineHeight: 1,
690
- padding: "0.08rem 0.32rem",
691
- fontSize: "0.5rem",
703
+ padding: "0.2rem 0.1rem",
704
+ marginBottom: -1,
705
+ fontSize: "0.58rem",
692
706
  fontWeight: 750,
693
707
  letterSpacing: "0.04em",
694
708
  textTransform: "uppercase",
695
- borderColor: "var(--border)",
696
- backgroundColor: { default: "transparent", ":hover": "var(--panel-3)" },
709
+ borderBottomWidth: 2,
710
+ borderBottomStyle: "solid",
711
+ borderBottomColor: "transparent",
712
+ borderRadius: 0,
713
+ backgroundColor: "transparent",
697
714
  color: "var(--muted)",
698
715
  cursor: "pointer",
699
716
  },
700
717
  conversationTabActive: {
701
- borderColor: "var(--pass)",
702
- backgroundColor: "var(--pass-soft)",
703
- color: "var(--pass)",
718
+ borderBottomColor: "var(--accent)",
719
+ color: "var(--text)",
704
720
  },
705
721
  conversationTabDot: {
706
722
  width: "0.28rem",
707
723
  height: "0.28rem",
708
724
  borderRadius: 999,
725
+ flex: "none",
726
+ },
727
+ conversationTabDotDone: { backgroundColor: "var(--pass)" },
728
+ conversationTabDotRunning: {
709
729
  backgroundColor: "var(--skip)",
710
730
  animationName: runningPulse,
711
731
  animationDuration: {
@@ -718,6 +738,7 @@ const styles = stylex.create({
718
738
  },
719
739
  animationTimingFunction: "ease-in-out",
720
740
  },
741
+ conversationTabDotStopped: { backgroundColor: "var(--fail)" },
721
742
  conversationThread: { display: "grid", gap: "0.55rem" },
722
743
  runningIndicator: {
723
744
  display: "inline-flex",
@@ -775,6 +796,106 @@ const styles = stylex.create({
775
796
  textTransform: "uppercase",
776
797
  },
777
798
  messageContent: { margin: 0, color: "var(--text)", fontSize: "0.79rem", lineHeight: 1.5 },
799
+ judgeQuestion: {
800
+ display: "grid",
801
+ justifySelf: "stretch",
802
+ gap: "0.45rem",
803
+ padding: "0.7rem",
804
+ border: "1px solid oklch(0.75 0.12 175 / 0.3)",
805
+ borderRadius: 10,
806
+ backgroundColor: "var(--accent-soft)",
807
+ },
808
+ judgeQuestionText: {
809
+ margin: 0,
810
+ color: "var(--text)",
811
+ fontSize: "0.79rem",
812
+ lineHeight: 1.5,
813
+ },
814
+ judgeContext: {
815
+ display: "grid",
816
+ gap: "0.4rem",
817
+ paddingTop: "0.55rem",
818
+ borderTop: "1px solid oklch(0.75 0.12 175 / 0.22)",
819
+ },
820
+ judgeContextHeader: {
821
+ display: "flex",
822
+ alignItems: "baseline",
823
+ justifyContent: "space-between",
824
+ gap: "0.75rem",
825
+ color: "var(--text)",
826
+ fontSize: "0.7rem",
827
+ },
828
+ judgeContextNote: { margin: 0, color: "var(--muted)", fontSize: "0.68rem", lineHeight: 1.4 },
829
+ judgeContextGroup: { display: "grid", gap: "0.35rem" },
830
+ judgeContextGroupHeading: {
831
+ margin: 0,
832
+ color: "var(--subtle)",
833
+ fontSize: "0.64rem",
834
+ fontWeight: 750,
835
+ letterSpacing: "0.04em",
836
+ textTransform: "uppercase",
837
+ },
838
+ judgeContextTabs: {
839
+ display: "flex",
840
+ flexWrap: "nowrap",
841
+ gap: "0.55rem",
842
+ overflowX: "auto",
843
+ scrollbarWidth: "none",
844
+ "::-webkit-scrollbar": { display: "none" },
845
+ borderBottomWidth: 1,
846
+ borderBottomStyle: "solid",
847
+ borderBottomColor: "oklch(0.75 0.12 175 / 0.22)",
848
+ },
849
+ judgeContextTab: {
850
+ flex: "0 0 auto",
851
+ minHeight: "1.75rem",
852
+ padding: "0.25rem 0.05rem",
853
+ marginBottom: -1,
854
+ borderBottomWidth: 2,
855
+ borderBottomStyle: "solid",
856
+ borderBottomColor: "transparent",
857
+ backgroundColor: { default: "transparent", ":hover": "var(--panel-3)" },
858
+ color: "var(--muted)",
859
+ fontSize: "0.58rem",
860
+ fontWeight: 750,
861
+ whiteSpace: "nowrap",
862
+ cursor: "pointer",
863
+ },
864
+ judgeContextTabActive: { borderBottomColor: "var(--accent)", color: "var(--text)" },
865
+ judgeContextAnswer: {
866
+ display: "grid",
867
+ gap: "0.3rem",
868
+ padding: "0.5rem",
869
+ borderRadius: 6,
870
+ backgroundColor: "var(--bg)",
871
+ },
872
+ judgeContextList: { display: "grid", gap: "0.3rem", margin: 0 },
873
+ judgeContextItem: {
874
+ display: "grid",
875
+ gridTemplateColumns: {
876
+ default: "minmax(7.5rem, 0.3fr) minmax(0, 1fr)",
877
+ "@media (max-width: 620px)": "1fr",
878
+ },
879
+ gap: "0.65rem",
880
+ padding: "0.4rem 0.5rem",
881
+ borderRadius: 6,
882
+ backgroundColor: "var(--bg)",
883
+ },
884
+ judgeContextLabel: {
885
+ color: "var(--subtle)",
886
+ fontSize: "0.64rem",
887
+ fontWeight: 750,
888
+ letterSpacing: "0.03em",
889
+ textTransform: "uppercase",
890
+ },
891
+ judgeContextValue: {
892
+ margin: 0,
893
+ color: "var(--text)",
894
+ fontSize: "0.72rem",
895
+ lineHeight: 1.45,
896
+ whiteSpace: "pre-wrap",
897
+ wordBreak: "break-word",
898
+ },
778
899
  judgeResponse: {
779
900
  display: "grid",
780
901
  justifySelf: "start",
@@ -1221,15 +1342,139 @@ function ComparisonColumn({ execution, label }) {
1221
1342
  return (_jsxs("article", { ...stylex.props(styles.card), children: [_jsxs("strong", { children: [label, ": ", execution.status] }), _jsx("p", { ...stylex.props(styles.meta), children: execution.id.slice(0, 8) }), execution.attempts.map((attempt) => (_jsx(AttemptCard, { attempt: attempt }, attempt.id)))] }));
1222
1343
  }
1223
1344
  function AttemptCard({ attempt, criteria = [] }) {
1224
- const output = [...attempt.output.stdout, ...attempt.output.stderr].join("\n");
1225
- const result = operationResult(attempt.operations);
1226
- const title = attempt.title.filter(Boolean).at(-1) ?? "Unnamed test";
1227
- const [selectedConversationId, setSelectedConversationId] = useState(attempt.operations[0]?.id);
1228
- const selectedConversation = attempt.operations.find((operation) => operation.id === selectedConversationId) ??
1229
- attempt.operations[0];
1230
- return (_jsxs("article", { ...stylex.props(styles.attemptCard), children: [_jsxs("div", { ...stylex.props(styles.row), children: [_jsxs("div", { children: [_jsx("strong", { ...stylex.props(styles.attemptTitle), children: title }), _jsxs("p", { ...stylex.props(styles.meta), children: [attempt.project || "default", " \u00B7 ", attempt.operations.length, " named", " ", pluralize(attempt.operations.length, "operation")] })] }), _jsxs("div", { ...stylex.props(styles.attemptActions), children: [attempt.status === "failed" ? (_jsx(CopyFailureButton, { attempt: attempt, criteria: criteria })) : null, _jsx("span", { ...stylex.props(styles.status, attempt.status === "passed" && styles.statusPass, attempt.status === "failed" && styles.statusFail, attempt.status === "running" && styles.statusRunning), children: attempt.status })] })] }), _jsx(OperationList, { operations: attempt.operations }), _jsx(RunResult, { result: result, status: attempt.status, criteria: criteria, explanations: judgeCriterionExplanations(attempt.operations), errors: attempt.errors, criterionResults: attempt.criterionResults }), attempt.operations.length > 0 ? (_jsx(ConversationTabs, { operations: attempt.operations, selected: selectedConversation, select: setSelectedConversationId, attemptStatus: attempt.status })) : null, _jsx(Conversation, { operation: selectedConversation, operations: attempt.operations, select: setSelectedConversationId, status: attempt.status === "running"
1231
- ? (selectedConversation?.status ?? attempt.status)
1232
- : attempt.status }), _jsx(TechnicalDetails, { operations: attempt.operations, output: output })] }));
1345
+ const { operations, output, title } = attemptPresentation(attempt, criteria);
1346
+ const agents = operations.filter((operation) => operation.kind !== "evaluation");
1347
+ const judges = operations.filter((operation) => operation.kind === "evaluation");
1348
+ return (_jsxs("article", { ...stylex.props(styles.attemptCard), children: [_jsxs("div", { ...stylex.props(styles.row), children: [_jsxs("div", { children: [_jsx("strong", { ...stylex.props(styles.attemptTitle), children: title }), _jsxs("p", { ...stylex.props(styles.meta), children: [attempt.project || "default", " \u00B7 ", attempt.operations.length, " named", " ", pluralize(attempt.operations.length, "operation")] })] }), _jsxs("div", { ...stylex.props(styles.attemptActions), children: [attempt.status === "failed" ? (_jsx(CopyFailureButton, { attempt: attempt, criteria: criteria })) : null, _jsx("span", { ...stylex.props(styles.status, attempt.status === "passed" && styles.statusPass, attempt.status === "failed" && styles.statusFail, attempt.status === "running" && styles.statusRunning), children: attempt.status })] })] }), _jsx(OperationList, { operations: operations }), _jsx(ConversationSection, { label: "Agents", sectionOperations: agents, allOperations: operations, attempt: attempt, criteria: criteria }), _jsx(ConversationSection, { label: "Judges", sectionOperations: judges, allOperations: operations, attempt: attempt, criteria: criteria }), _jsx(TechnicalDetails, { operations: operations, output: output })] }));
1349
+ }
1350
+ function ConversationSection({ label, sectionOperations, allOperations, attempt, criteria, }) {
1351
+ const [selectedId, setSelectedId] = useState(sectionOperations[0]?.id);
1352
+ if (!sectionOperations.length && (allOperations.length > 0 || label === "Judges"))
1353
+ return null;
1354
+ const selected = sectionOperations.find((operation) => operation.id === selectedId) ?? sectionOperations[0];
1355
+ const status = attempt.status === "running" ? (selected?.status ?? attempt.status) : attempt.status;
1356
+ return (_jsx(Conversation, { label: label, operation: selected, operations: sectionOperations, allOperations: allOperations, select: setSelectedId, status: status, attemptStatus: attempt.status, result: _jsx(AttemptResult, { attempt: attempt, criteria: criteria, operation: selected, operations: allOperations, result: selectedOperationResult(selected) }) }));
1357
+ }
1358
+ function attemptPresentation(attempt, criteria) {
1359
+ return {
1360
+ operations: orderedAttemptOperations(attempt.operations, criteria),
1361
+ output: [...attempt.output.stdout, ...attempt.output.stderr].join("\n"),
1362
+ title: attempt.title.filter(Boolean).at(-1) ?? "Unnamed test",
1363
+ };
1364
+ }
1365
+ function selectedOperationResult(operation) {
1366
+ return operationResult(operation ? [operation] : []);
1367
+ }
1368
+ function orderedAttemptOperations(operations, criteria) {
1369
+ const entries = operations.map((operation, index) => ({
1370
+ operation,
1371
+ index,
1372
+ invocationIndex: operationInvocationIndex(operation),
1373
+ }));
1374
+ if (entries.some((entry) => entry.invocationIndex !== undefined))
1375
+ return entries
1376
+ .sort((left, right) => (left.invocationIndex ?? Number.POSITIVE_INFINITY) -
1377
+ (right.invocationIndex ?? Number.POSITIVE_INFINITY) || left.index - right.index)
1378
+ .map((entry) => entry.operation);
1379
+ return entries
1380
+ .sort((left, right) => {
1381
+ if (operationIdentity(left.operation) !== operationIdentity(right.operation))
1382
+ return left.index - right.index;
1383
+ const leftOrdinal = inferredOperationOrdinal(criteria, left.operation);
1384
+ const rightOrdinal = inferredOperationOrdinal(criteria, right.operation);
1385
+ if (leftOrdinal === undefined || rightOrdinal === undefined)
1386
+ return left.index - right.index;
1387
+ return leftOrdinal - rightOrdinal || left.index - right.index;
1388
+ })
1389
+ .map((entry) => entry.operation);
1390
+ }
1391
+ function operationInvocationIndex(operation) {
1392
+ if (typeof operation.invocationIndex === "number")
1393
+ return operation.invocationIndex;
1394
+ return operation.data.flatMap((value) => {
1395
+ if (!isRecord(value) || typeof value.invocationIndex !== "number")
1396
+ return [];
1397
+ return [value.invocationIndex];
1398
+ })[0];
1399
+ }
1400
+ function AttemptResult({ attempt, criteria, operation, operations, result, }) {
1401
+ const hasJudge = operations.some((item) => item.kind === "evaluation");
1402
+ const includeShared = attempt.status !== "running" && (operation?.kind === "evaluation" || !hasJudge);
1403
+ const indexes = selectedCriterionIndexes({ criteria, operation, operations, includeShared });
1404
+ return (_jsx(RunResult, { result: result, status: attempt.status, criteria: selectIndexes(criteria, indexes), explanations: selectOptionalIndexes(operationCriterionExplanations(criteria, operation), indexes), errors: attempt.errors, criterionResults: selectIndexes(attempt.criterionResults, indexes) }));
1405
+ }
1406
+ function selectedCriterionIndexes(input) {
1407
+ const { criteria, operation, operations, includeShared } = input;
1408
+ const recorded = operationCriterionIndexes(criteria, operation);
1409
+ const allRecorded = new Set(operations.flatMap((item) => operationCriterionIndexes(criteria, item)));
1410
+ if (allRecorded.size)
1411
+ return criteria.flatMap((_, index) => recorded.includes(index) || (includeShared && !allRecorded.has(index)) ? [index] : []);
1412
+ if (!includeShared)
1413
+ return [];
1414
+ return inferredRepeatedRunCriterionIndexes(criteria, operation, operations);
1415
+ }
1416
+ function operationCriterionIndexes(criteria, operation) {
1417
+ return [
1418
+ ...new Set(operation?.data.flatMap((item) => criterionRecordIndexes(criteria, item)) ?? []),
1419
+ ];
1420
+ }
1421
+ function criterionRecordIndexes(criteria, value) {
1422
+ if (!isRecord(value))
1423
+ return [];
1424
+ if (typeof value.criterion === "string") {
1425
+ const index = criteria.indexOf(value.criterion);
1426
+ return index < 0 ? [] : [index];
1427
+ }
1428
+ if (Array.isArray(value.criterionIndexes))
1429
+ return value.criterionIndexes.filter((index) => Number.isInteger(index));
1430
+ return Number.isInteger(value.criterionIndex) ? [Number(value.criterionIndex)] : [];
1431
+ }
1432
+ const RUN_ORDINALS = ["first", "second", "third", "fourth", "fifth"];
1433
+ const CRITERION_WORD_SEPARATOR = /[^a-z0-9]+/;
1434
+ function inferredRepeatedRunCriterionIndexes(criteria, operation, operations) {
1435
+ if (!operation)
1436
+ return undefined;
1437
+ const runs = operations.filter((item) => operationIdentity(item) === operationIdentity(operation));
1438
+ if (runs.length < 2)
1439
+ return undefined;
1440
+ const selected = inferredOperationOrdinal(criteria, operation) ??
1441
+ runs.findIndex((run) => run.id === operation.id);
1442
+ const scopes = criteria.map(criterionRunOrdinal);
1443
+ if (selected < 0 || !scopes.some((scope) => scope !== undefined))
1444
+ return undefined;
1445
+ return scopes.flatMap((scope, index) => scope === undefined || scope === selected ? [index] : []);
1446
+ }
1447
+ function inferredOperationOrdinal(criteria, operation) {
1448
+ const output = operationResult([operation]).output?.trim().toLowerCase();
1449
+ if (!output)
1450
+ return undefined;
1451
+ const matches = criteria.flatMap((criterion) => {
1452
+ const ordinal = criterionRunOrdinal(criterion);
1453
+ const score = criterionOutputScore(criterion, output);
1454
+ return ordinal === undefined || score === 0 ? [] : [{ ordinal, score }];
1455
+ });
1456
+ const bestScore = Math.max(0, ...matches.map((match) => match.score));
1457
+ const ordinals = new Set(matches.filter((match) => match.score === bestScore).map((match) => match.ordinal));
1458
+ return ordinals.size === 1 ? [...ordinals][0] : undefined;
1459
+ }
1460
+ function criterionOutputScore(criterion, output) {
1461
+ const normalized = criterion.toLowerCase();
1462
+ if (normalized.includes(`exactly ${output}`))
1463
+ return 2;
1464
+ return normalized.split(CRITERION_WORD_SEPARATOR).includes(output) ? 1 : 0;
1465
+ }
1466
+ function criterionRunOrdinal(criterion) {
1467
+ const normalized = criterion.toLowerCase();
1468
+ const index = RUN_ORDINALS.findIndex((ordinal) => normalized.includes(`${ordinal} `));
1469
+ return index < 0 ? undefined : index;
1470
+ }
1471
+ function selectIndexes(values, indexes) {
1472
+ return indexes
1473
+ ? indexes.flatMap((index) => (values[index] === undefined ? [] : [values[index]]))
1474
+ : values;
1475
+ }
1476
+ function selectOptionalIndexes(values, indexes) {
1477
+ return indexes ? indexes.map((index) => values[index]) : values;
1233
1478
  }
1234
1479
  function CopyFailureButton({ attempt, criteria }) {
1235
1480
  const [state, setState] = useState("idle");
@@ -1570,9 +1815,9 @@ function ResultMetrics({ result }) {
1570
1815
  ];
1571
1816
  return (_jsx("div", { ...stylex.props(styles.metricGrid), children: metrics.map(([label, value]) => (_jsxs("div", { ...stylex.props(styles.metric), children: [_jsx("span", { ...stylex.props(styles.resourceFactLabel), children: label }), _jsx("span", { ...stylex.props(styles.metricValue), children: value })] }, label))) }));
1572
1817
  }
1573
- function Conversation({ operation, operations, select, status, }) {
1818
+ function Conversation({ label, operation, operations, allOperations, select, status, attemptStatus, result, }) {
1574
1819
  const { timeline, judge } = conversationContent(operation, operations);
1575
- return (_jsxs("section", { "aria-label": "Conversation", ...stylex.props(styles.conversation), children: [_jsxs("header", { ...stylex.props(styles.conversationHeader), children: [_jsx("h3", { ...stylex.props(styles.contentHeading), children: "Conversation" }), _jsx(ConversationRunTabs, { operation: operation, operations: operations, select: select })] }), _jsxs("div", { ...stylex.props(styles.conversationThread), children: [_jsx(ConversationEvents, { timeline: timeline }), _jsx(JudgeResponse, { review: judge })] }), timeline.length === 0 && status !== "running" ? (_jsx("p", { ...stylex.props(styles.meta), children: conversationPlaceholder(status) })) : null, status === "running" ? _jsx(RunningIndicator, {}) : null] }));
1820
+ return (_jsx("section", { "aria-label": label, ...stylex.props(styles.conversation), children: _jsxs("div", { ...stylex.props(styles.conversationSurface), children: [_jsx("header", { ...stylex.props(styles.conversationHeader), children: _jsx("h3", { ...stylex.props(styles.contentHeading), children: label }) }), _jsx(ConversationTabs, { label: label, operations: operations, selected: operation, select: select, attemptStatus: attemptStatus }), _jsx(ConversationRunTabs, { operation: operation, operations: operations, select: select }), operation?.kind === "evaluation" ? null : result, _jsxs("section", { "aria-label": `${label} messages`, ...stylex.props(styles.conversationThread), children: [judge ? (_jsx(JudgeQuestion, { review: judge, operations: allOperations })) : (_jsx(ConversationEvents, { timeline: timeline })), _jsx(JudgeResponse, { review: judge, operations: allOperations })] }), timeline.length === 0 && status !== "running" ? (_jsx("p", { ...stylex.props(styles.meta), children: conversationPlaceholder(status) })) : null, status === "running" ? _jsx(RunningIndicator, { kind: operation?.kind }) : null] }) }));
1576
1821
  }
1577
1822
  function conversationContent(operation, operations) {
1578
1823
  if (operation?.kind === "evaluation") {
@@ -1604,13 +1849,89 @@ function recordedTimeline(result) {
1604
1849
  function ConversationEvents({ timeline }) {
1605
1850
  return timeline.map((event, index) => event.kind === "tool" ? (_jsx(ToolActivity, { tool: event.tool }, `tool:${event.tool.seq ?? index}:${event.tool.name}`)) : (_jsx(MessageBubble, { message: event.message }, `message:${event.message.seq ?? index}:${event.message.role}`)));
1606
1851
  }
1607
- function JudgeResponse({ review }) {
1852
+ function JudgeQuestion({ review, operations, }) {
1853
+ if (!review.question && !review.context.length)
1854
+ return null;
1855
+ return (_jsxs("section", { "aria-label": "Judge question", ...stylex.props(styles.judgeQuestion), children: [_jsx("h4", { ...stylex.props(styles.judgeResponseHeading), children: "Judge question" }), review.question ? (_jsx("p", { ...stylex.props(styles.judgeQuestionText), children: review.question })) : null, review.context.length ? (_jsx(JudgeContext, { context: review.context, operations: operations })) : null] }));
1856
+ }
1857
+ function JudgeContext({ context, operations, }) {
1858
+ const { answers, supplied } = judgeContextGroups(context, operations);
1859
+ const [selectedId, setSelectedId] = useState(answers[0]?.id);
1860
+ const selected = answers.find((item) => item.id === selectedId) ?? answers[0];
1861
+ const panelId = useId();
1862
+ return (_jsxs("section", { "aria-label": "Context included", ...stylex.props(styles.judgeContext), children: [_jsxs("div", { ...stylex.props(styles.judgeContextHeader), children: [_jsx("strong", { children: "Context included" }), _jsxs("span", { children: [context.length, " selected fields"] })] }), _jsx("p", { ...stylex.props(styles.judgeContextNote), children: "Only these explicitly selected fields were sent to this judge." }), answers.length ? (_jsxs("section", { "aria-label": "Agent answers", ...stylex.props(styles.judgeContextGroup), children: [_jsx("h5", { ...stylex.props(styles.judgeContextGroupHeading), children: "Agent answers" }), _jsx("div", { role: "tablist", "aria-label": "Agent answers included", ...stylex.props(styles.judgeContextTabs), children: answers.map((answer, index) => (_jsx("button", { type: "button", role: "tab", "aria-selected": answer.id === selected?.id, "aria-controls": panelId, ...stylex.props(styles.buttonReset, styles.judgeContextTab, answer.id === selected?.id && styles.judgeContextTabActive), onClick: () => setSelectedId(answer.id), onKeyDown: (event) => selectContextWithKeyboard({ event, answers, index, select: setSelectedId }), children: answer.tabLabel }, answer.id))) }), selected ? (_jsx("div", { id: panelId, role: "tabpanel", "aria-label": `Agent response from ${selected.tabLabel}`, ...stylex.props(styles.judgeContextAnswer), children: _jsx("div", { ...stylex.props(styles.judgeContextValue), children: _jsx("div", { className: "message-markdown", children: _jsx(Markdown, { children: normalizeTranscriptMarkdown(selected.value) }) }) }) })) : null] })) : null, supplied.length ? (_jsxs("section", { "aria-label": "Additional context", ...stylex.props(styles.judgeContextGroup), children: [_jsx("h5", { ...stylex.props(styles.judgeContextGroupHeading), children: "Additional context" }), _jsx("dl", { ...stylex.props(styles.judgeContextList), children: supplied.map((item) => (_jsxs("div", { ...stylex.props(styles.judgeContextItem), children: [_jsx("dt", { ...stylex.props(styles.judgeContextLabel), children: item.label }), _jsx("dd", { ...stylex.props(styles.judgeContextValue), children: item.value })] }, item.label))) })] })) : null] }));
1863
+ }
1864
+ function judgeContextGroups(context, operations) {
1865
+ const candidates = operations
1866
+ .filter((operation) => operation.kind !== "evaluation")
1867
+ .map((operation) => ({ operation, output: operationOutput(operation) }));
1868
+ const claimed = new Set();
1869
+ const answers = [];
1870
+ const supplied = [];
1871
+ for (const [index, item] of context.entries()) {
1872
+ const match = candidates.find((candidate) => !claimed.has(candidate.operation.id) && sameContextValue(item.rawValue, candidate.output));
1873
+ if (!match) {
1874
+ supplied.push(item);
1875
+ continue;
1876
+ }
1877
+ claimed.add(match.operation.id);
1878
+ answers.push({
1879
+ ...item,
1880
+ id: `${match.operation.id}:${index}`,
1881
+ tabLabel: agentRunLabel(match.operation, operations),
1882
+ });
1883
+ }
1884
+ return { answers, supplied };
1885
+ }
1886
+ function operationOutput(operation) {
1887
+ const records = operation.data.filter(isRecord);
1888
+ return [...records].reverse().find((record) => record.output !== undefined)?.output;
1889
+ }
1890
+ function sameContextValue(left, right) {
1891
+ if (right === undefined)
1892
+ return false;
1893
+ if (left === right)
1894
+ return true;
1895
+ try {
1896
+ return JSON.stringify(left) === JSON.stringify(right);
1897
+ }
1898
+ catch {
1899
+ return false;
1900
+ }
1901
+ }
1902
+ function agentRunLabel(operation, operations) {
1903
+ const peers = operations.filter((item) => item.kind !== "evaluation" && operationIdentity(item) === operationIdentity(operation));
1904
+ const name = conversationLabel(operation);
1905
+ if (peers.length < 2)
1906
+ return name;
1907
+ return `${name} · Run ${peers.findIndex((item) => item.id === operation.id) + 1}`;
1908
+ }
1909
+ function selectContextWithKeyboard(input) {
1910
+ const { event, answers, index, select } = input;
1911
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight")
1912
+ return;
1913
+ event.preventDefault();
1914
+ const direction = event.key === "ArrowRight" ? 1 : -1;
1915
+ const nextIndex = (index + direction + answers.length) % answers.length;
1916
+ const next = answers[nextIndex];
1917
+ if (!next)
1918
+ return;
1919
+ select(next.id);
1920
+ event.currentTarget.parentElement
1921
+ ?.querySelectorAll("[role=tab]")[nextIndex]?.focus();
1922
+ }
1923
+ function JudgeResponse({ review, operations, }) {
1608
1924
  if (!review || (!review.outcomes.length && !review.reason))
1609
1925
  return null;
1610
- return _jsx(JudgeFindings, { outcomes: review.outcomes, reason: review.reason });
1926
+ const { answers } = judgeContextGroups(review.context, operations);
1927
+ return _jsx(JudgeFindings, { outcomes: review.outcomes, reason: review.reason, answers: answers });
1611
1928
  }
1612
- function JudgeFindings({ outcomes, reason, }) {
1613
- return (_jsxs("section", { "aria-label": "Judge response", ...stylex.props(styles.judgeResponse), children: [_jsx("h4", { ...stylex.props(styles.judgeResponseHeading), children: "Judge response" }), _jsx("div", { ...stylex.props(styles.judgeOutcomeList), children: outcomes.map((outcome) => (_jsxs("div", { ...stylex.props(styles.judgeOutcome), children: [_jsx(CriterionOutcomeIcon, { outcome: outcome.passed ? "passed" : "failed", label: outcome.passed ? "Passed" : "Failed" }), _jsx("span", { ...stylex.props(styles.judgeOutcomeLabel), children: outcome.label }), outcome.explanation ? (_jsx("p", { ...stylex.props(styles.judgeOutcomeExplanation), children: outcome.explanation })) : null] }, outcome.label))) }), reason && !outcomes.length ? (_jsx("p", { ...stylex.props(styles.judgeOutcomeExplanation), children: reason })) : null] }));
1929
+ function JudgeFindings({ outcomes, reason, answers = [], }) {
1930
+ return (_jsxs("section", { "aria-label": "Judge response", ...stylex.props(styles.judgeResponse), children: [_jsx("h4", { ...stylex.props(styles.judgeResponseHeading), children: "Judge response" }), _jsx("div", { ...stylex.props(styles.judgeOutcomeList), children: outcomes.map((outcome) => (_jsxs("div", { ...stylex.props(styles.judgeOutcome), children: [_jsx(CriterionOutcomeIcon, { outcome: outcome.passed ? "passed" : "failed", label: outcome.passed ? "Passed" : "Failed" }), _jsx("span", { ...stylex.props(styles.judgeOutcomeLabel), children: judgeOutcomeLabel(outcome.label, answers) }), outcome.explanation ? (_jsx("p", { ...stylex.props(styles.judgeOutcomeExplanation), children: outcome.explanation })) : null] }, outcome.label))) }), reason && !outcomes.length ? (_jsx("p", { ...stylex.props(styles.judgeOutcomeExplanation), children: reason })) : null] }));
1931
+ }
1932
+ function judgeOutcomeLabel(label, answers) {
1933
+ const answer = answers.find((item) => label === `${item.label} Correct` || label === item.label);
1934
+ return answer?.tabLabel ?? label;
1614
1935
  }
1615
1936
  function judgeReviewData(operation) {
1616
1937
  const records = operation.data.filter(isRecord);
@@ -1619,10 +1940,31 @@ function judgeReviewData(operation) {
1619
1940
  const reason = optionalString(output.reason) ?? conversationOutput(record?.output);
1620
1941
  return {
1621
1942
  question: judgeQuestion(record),
1943
+ context: judgeContext(record?.input),
1622
1944
  outcomes: judgeOutcomes(output, reason),
1623
1945
  reason,
1624
1946
  };
1625
1947
  }
1948
+ function judgeContext(value) {
1949
+ if (value === undefined)
1950
+ return [];
1951
+ if (!isRecord(value))
1952
+ return [{ label: "Selected input", value: judgeContextValue(value), rawValue: value }];
1953
+ return Object.entries(value).map(([key, item]) => ({
1954
+ label: humanizeKey(key),
1955
+ value: judgeContextValue(item),
1956
+ rawValue: item,
1957
+ }));
1958
+ }
1959
+ function judgeContextValue(value) {
1960
+ if (typeof value === "string")
1961
+ return value;
1962
+ if (value === null || value === undefined)
1963
+ return String(value);
1964
+ if (typeof value === "number" || typeof value === "boolean")
1965
+ return String(value);
1966
+ return JSON.stringify(value, null, 2);
1967
+ }
1626
1968
  function judgeQuestion(record) {
1627
1969
  const evaluation = isRecord(record?.evaluation) ? record.evaluation : undefined;
1628
1970
  return optionalString(evaluation?.prompt);
@@ -1650,10 +1992,16 @@ function judgeOutcomeExplanation(output, key) {
1650
1992
  optionalString(output[`${key}Explanation`]) ??
1651
1993
  optionalString(output[`${key}Reason`]));
1652
1994
  }
1653
- function judgeCriterionExplanations(operations) {
1654
- return operations
1655
- .flatMap((operation) => operation.kind === "evaluation" ? judgeReviewData(operation).outcomes : [])
1656
- .map((outcome) => outcome.explanation);
1995
+ function operationCriterionExplanations(criteria, operation) {
1996
+ const explanations = Array(criteria.length);
1997
+ if (operation?.kind !== "evaluation")
1998
+ return explanations;
1999
+ const indexes = operationCriterionIndexes(criteria, operation);
2000
+ const outcomes = judgeReviewData(operation).outcomes;
2001
+ indexes.forEach((criterionIndex, outcomeIndex) => {
2002
+ explanations[criterionIndex] = outcomes[outcomeIndex]?.explanation;
2003
+ });
2004
+ return explanations;
1657
2005
  }
1658
2006
  function legacyJudgeInput(operation, operations) {
1659
2007
  if (!operation || operation.kind !== "evaluation")
@@ -1681,25 +2029,24 @@ function legacyJudgeInput(operation, operations) {
1681
2029
  });
1682
2030
  return timeline;
1683
2031
  }
1684
- function ConversationTabs({ operations, selected, select, attemptStatus, }) {
2032
+ function ConversationTabs({ label, operations, selected, select, attemptStatus, }) {
1685
2033
  if (!operations.length)
1686
2034
  return null;
1687
2035
  const groups = conversationGroups(operations);
1688
2036
  const representatives = groups.map((group) => group.operations[0]);
1689
2037
  const selectedGroup = selected ? operationIdentity(selected) : undefined;
1690
- return (_jsx("div", { role: "tablist", "aria-label": "Conversation participants", ...stylex.props(styles.conversationSwitcher, styles.conversationTabs), children: groups.map((group, index) => (_jsx(ConversationTab, { operation: group.operations[0], active: group.key === selectedGroup, running: attemptStatus === "running" &&
1691
- group.operations.some((operation) => operation.status === "running"), onSelect: () => select(group.operations[0].id), onKeyDown: (event) => selectConversationWithKeyboard({
2038
+ return (_jsx("div", { role: "tablist", "aria-label": `${label} participants`, ...stylex.props(styles.conversationSwitcher, styles.conversationTabs), children: groups.map((group, index) => (_jsx(ConversationTab, { operation: group.operations[0], active: group.key === selectedGroup, status: conversationGroupStatus(group.operations, attemptStatus), onSelect: () => select(group.operations[0].id), onKeyDown: (event) => selectConversationWithKeyboard({
1692
2039
  event,
1693
2040
  operations: representatives,
1694
2041
  index,
1695
2042
  select,
1696
2043
  }) }, group.key))) }));
1697
2044
  }
1698
- function ConversationTab({ operation, active, running, onSelect, onKeyDown, }) {
1699
- return (_jsxs("button", { type: "button", role: "tab", "aria-label": conversationLabel(operation), "aria-selected": active, ...stylex.props(styles.buttonReset, styles.status, styles.conversationTab, active && styles.conversationTabActive), onClick: onSelect, onKeyDown: onKeyDown, children: [running ? (_jsx("span", { role: "status", "aria-label": `${operation?.name ?? "Agent"} is running`, ...stylex.props(styles.conversationTabDot) })) : null, conversationLabel(operation)] }));
2045
+ function ConversationTab({ operation, active, status, onSelect, onKeyDown, }) {
2046
+ return (_jsxs("button", { type: "button", role: "tab", "aria-label": conversationLabel(operation), "aria-selected": active, ...stylex.props(styles.buttonReset, styles.conversationTab, active && styles.conversationTabActive), onClick: onSelect, onKeyDown: onKeyDown, children: [_jsx("span", { role: "status", "aria-label": `${operation?.name ?? "Operation"} is ${status}`, ...stylex.props(styles.conversationTabDot, status === "done" && styles.conversationTabDotDone, status === "running" && styles.conversationTabDotRunning, status === "stopped" && styles.conversationTabDotStopped) }), conversationLabel(operation)] }));
1700
2047
  }
1701
2048
  function conversationLabel(operation) {
1702
- return `${operation?.name ?? "Unnamed"} · ${operation?.kind === "evaluation" ? "Judge" : "Agent"}`;
2049
+ return operation?.name ?? "Unnamed";
1703
2050
  }
1704
2051
  function conversationGroups(operations) {
1705
2052
  const groups = new Map();
@@ -1713,6 +2060,17 @@ function conversationGroups(operations) {
1713
2060
  }
1714
2061
  return [...groups.values()];
1715
2062
  }
2063
+ function conversationGroupStatus(operations, attemptStatus) {
2064
+ if (operations.some((operation) => operation.status === "failed" || operation.status === "interrupted"))
2065
+ return "stopped";
2066
+ if (operations.some((operation) => operation.status === "running")) {
2067
+ if (attemptStatus === "running")
2068
+ return "running";
2069
+ if (attemptStatus === "interrupted" || attemptStatus === "failed")
2070
+ return "stopped";
2071
+ }
2072
+ return "done";
2073
+ }
1716
2074
  function operationIdentity(operation) {
1717
2075
  return `${operation.name ?? "Unnamed"}\u0000${operation.kind}`;
1718
2076
  }
@@ -1737,8 +2095,9 @@ function selectConversationWithKeyboard(input) {
1737
2095
  const tabs = event.currentTarget.parentElement?.querySelectorAll("[role=tab]");
1738
2096
  tabs?.[(index + direction + operations.length) % operations.length]?.focus();
1739
2097
  }
1740
- function RunningIndicator() {
1741
- return (_jsxs("div", { role: "status", "aria-label": "Agent is running", ...stylex.props(styles.runningIndicator), children: [_jsx("span", { children: "Running" }), _jsxs("span", { "aria-hidden": "true", ...stylex.props(styles.runningDots), children: [_jsx("span", { ...stylex.props(styles.runningDot), children: "\u2022" }), _jsx("span", { ...stylex.props(styles.runningDot, styles.runningDotSecond), children: "\u2022" }), _jsx("span", { ...stylex.props(styles.runningDot, styles.runningDotThird), children: "\u2022" })] })] }));
2098
+ function RunningIndicator({ kind }) {
2099
+ const participant = kind === "evaluation" ? "Judge" : "Agent";
2100
+ return (_jsxs("div", { role: "status", "aria-label": `${participant} is running`, ...stylex.props(styles.runningIndicator), children: [_jsx("span", { children: "Running" }), _jsxs("span", { "aria-hidden": "true", ...stylex.props(styles.runningDots), children: [_jsx("span", { ...stylex.props(styles.runningDot), children: "\u2022" }), _jsx("span", { ...stylex.props(styles.runningDot, styles.runningDotSecond), children: "\u2022" }), _jsx("span", { ...stylex.props(styles.runningDot, styles.runningDotThird), children: "\u2022" })] })] }));
1742
2101
  }
1743
2102
  function ToolActivity({ tool }) {
1744
2103
  const presentation = toolPresentation(tool);
@@ -1798,18 +2157,18 @@ function OperationList({ operations }) {
1798
2157
  if (operations.length === 0)
1799
2158
  return null;
1800
2159
  const summaries = summarizeOperations(operations);
1801
- return (_jsx("div", { ...stylex.props(styles.list), children: summaries.map((summary) => (_jsxs("div", { ...stylex.props(styles.meta), children: [summary.name, " \u00B7 ", summary.status, summary.count > 1 ? ` ×${summary.count}` : ""] }, summary.key))) }));
2160
+ return (_jsx("div", { ...stylex.props(styles.list), children: summaries.map((summary) => (_jsxs("div", { ...stylex.props(styles.meta), children: [summary.name, summary.count > 1 ? ` ×${summary.count}` : ""] }, summary.key))) }));
1802
2161
  }
1803
2162
  function summarizeOperations(operations) {
1804
2163
  const summaries = new Map();
1805
2164
  for (const operation of operations) {
1806
2165
  const name = operation.name ?? operation.kind;
1807
- const key = `${operationIdentity(operation)}\u0000${operation.status}`;
2166
+ const key = operationIdentity(operation);
1808
2167
  const existing = summaries.get(key);
1809
2168
  if (existing)
1810
2169
  existing.count++;
1811
2170
  else
1812
- summaries.set(key, { key, name, status: operation.status, count: 1 });
2171
+ summaries.set(key, { key, name, count: 1 });
1813
2172
  }
1814
2173
  return [...summaries.values()];
1815
2174
  }