@post-print/agent-test 2.1.0 → 2.2.1

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.
Files changed (46) hide show
  1. package/README.md +2 -2
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/sdk/execution-reporter.d.ts.map +1 -1
  7. package/dist/sdk/execution-reporter.js +4 -0
  8. package/dist/sdk/execution-reporter.js.map +1 -1
  9. package/dist/sdk/execution-store.d.ts +9 -0
  10. package/dist/sdk/execution-store.d.ts.map +1 -1
  11. package/dist/sdk/execution-store.js +27 -5
  12. package/dist/sdk/execution-store.js.map +1 -1
  13. package/dist/sdk/reporter.js +5 -1
  14. package/dist/sdk/reporter.js.map +1 -1
  15. package/dist/sdk/test.d.ts +7 -1
  16. package/dist/sdk/test.d.ts.map +1 -1
  17. package/dist/sdk/test.js +14 -2
  18. package/dist/sdk/test.js.map +1 -1
  19. package/dist/viewer/client-entry.js +23 -21
  20. package/dist/viewer/elapsed-time.d.ts +5 -0
  21. package/dist/viewer/elapsed-time.d.ts.map +1 -0
  22. package/dist/viewer/elapsed-time.js +16 -0
  23. package/dist/viewer/elapsed-time.js.map +1 -0
  24. package/dist/viewer/presentation.d.ts +20 -0
  25. package/dist/viewer/presentation.d.ts.map +1 -1
  26. package/dist/viewer/presentation.js +52 -0
  27. package/dist/viewer/presentation.js.map +1 -1
  28. package/dist/viewer/run-inbox.d.ts.map +1 -1
  29. package/dist/viewer/run-inbox.js +12 -5
  30. package/dist/viewer/run-inbox.js.map +1 -1
  31. package/dist/viewer/server.d.ts.map +1 -1
  32. package/dist/viewer/server.js +52 -4
  33. package/dist/viewer/server.js.map +1 -1
  34. package/dist/viewer/stylex.css +28 -4
  35. package/dist/viewer/test-catalog.d.ts +2 -0
  36. package/dist/viewer/test-catalog.d.ts.map +1 -1
  37. package/dist/viewer/test-catalog.js +1 -0
  38. package/dist/viewer/test-catalog.js.map +1 -1
  39. package/dist/viewer/v2-client.d.ts.map +1 -1
  40. package/dist/viewer/v2-client.js +227 -27
  41. package/dist/viewer/v2-client.js.map +1 -1
  42. package/dist/viewer/viewer-queries.d.ts +1 -0
  43. package/dist/viewer/viewer-queries.d.ts.map +1 -1
  44. package/dist/viewer/viewer-queries.js +7 -1
  45. package/dist/viewer/viewer-queries.js.map +1 -1
  46. package/package.json +2 -2
@@ -4,7 +4,8 @@ import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/rea
4
4
  import { createRootRoute, createRoute, createRouter, Link, Outlet, useNavigate, } from "@tanstack/react-router";
5
5
  import { createContext, useCallback, useContext, useEffect, useId, useRef, useState, } from "react";
6
6
  import Markdown from "react-markdown";
7
- import { assertionComparison, conversationPlaceholder, formatRunDate, median, preferredTestExecutionId, stripAnsi, testRunStatus, unwrapShellCommand, } from "./presentation.js";
7
+ import { ElapsedTime } from "./elapsed-time.js";
8
+ import { assertionComparison, conversationPlaceholder, describeShape, formatRunDate, median, preferredTestExecutionId, stripAnsi, testIntent, testRunStatus, unwrapShellCommand, } from "./presentation.js";
8
9
  import { RunInboxPage, RunPage } from "./run-inbox.js";
9
10
  import { ToolImages, toolImages } from "./tool-images.js";
10
11
  import { cancelExecution, fetchExecution as fetchExecutionRecord, startSuite, startTest, startTestGroup, useCatalog, useExecutionHistory, } from "./viewer-queries.js";
@@ -363,6 +364,18 @@ const styles = stylex.create({
363
364
  letterSpacing: "0.08em",
364
365
  textTransform: "uppercase",
365
366
  },
367
+ catalogNotice: {
368
+ margin: "0.6rem 1.25rem 0",
369
+ color: "var(--muted)",
370
+ fontSize: "0.72rem",
371
+ lineHeight: 1.45,
372
+ },
373
+ catalogNoticeError: {
374
+ padding: "0.45rem 0.6rem",
375
+ borderRadius: 8,
376
+ color: "var(--fail)",
377
+ backgroundColor: "var(--fail-soft)",
378
+ },
366
379
  groupCount: { color: "var(--subtle)", fontSize: "0.66rem", fontVariantNumeric: "tabular-nums" },
367
380
  navLink: {
368
381
  display: "block",
@@ -431,6 +444,16 @@ const styles = stylex.create({
431
444
  alignSelf: "start",
432
445
  },
433
446
  description: { color: "var(--muted)", fontSize: "0.82rem", lineHeight: 1.5, maxWidth: "54rem" },
447
+ suiteDescription: {
448
+ margin: 0,
449
+ color: "var(--subtle)",
450
+ fontSize: "0.74rem",
451
+ lineHeight: 1.45,
452
+ overflow: "hidden",
453
+ textOverflow: "ellipsis",
454
+ whiteSpace: "nowrap",
455
+ },
456
+ headerChecks: { paddingTop: "0.55rem" },
434
457
  path: {
435
458
  color: "var(--subtle)",
436
459
  fontSize: "0.7rem",
@@ -632,6 +655,46 @@ const styles = stylex.create({
632
655
  gap: "0.7rem",
633
656
  },
634
657
  attemptTitle: { fontSize: "0.84rem", lineHeight: 1.4 },
658
+ liveStrip: {
659
+ position: "sticky",
660
+ top: 0,
661
+ zIndex: 2,
662
+ display: "grid",
663
+ gap: "0.3rem",
664
+ padding: "0.55rem 0.75rem",
665
+ border: "1px solid var(--border-strong)",
666
+ borderRadius: 10,
667
+ backgroundColor: "var(--panel-2)",
668
+ minWidth: 0,
669
+ },
670
+ liveStripLine: {
671
+ display: "flex",
672
+ flexWrap: "wrap",
673
+ alignItems: "center",
674
+ gap: "0.4rem 0.75rem",
675
+ },
676
+ liveStripElapsed: {
677
+ color: "var(--text)",
678
+ fontSize: "0.82rem",
679
+ fontWeight: 700,
680
+ fontVariantNumeric: "tabular-nums",
681
+ },
682
+ liveStripMeta: { color: "var(--muted)", fontSize: "0.76rem", fontVariantNumeric: "tabular-nums" },
683
+ liveStripNow: {
684
+ margin: 0,
685
+ minWidth: 0,
686
+ overflow: "hidden",
687
+ textOverflow: "ellipsis",
688
+ whiteSpace: "nowrap",
689
+ color: "var(--text)",
690
+ fontSize: "0.78rem",
691
+ },
692
+ liveStripLabel: { color: "var(--subtle)", fontWeight: 650 },
693
+ liveStripDetail: {
694
+ marginLeft: "0.4rem",
695
+ color: "var(--muted)",
696
+ fontSize: "0.72rem",
697
+ },
635
698
  verdictPanel: {
636
699
  display: "grid",
637
700
  gap: "0.55rem",
@@ -652,6 +715,16 @@ const styles = stylex.create({
652
715
  margin: 0,
653
716
  },
654
717
  verdictMetric: { display: "grid", gap: "0.1rem", minWidth: 0 },
718
+ verdictAttachments: {
719
+ display: "flex",
720
+ flexWrap: "wrap",
721
+ gap: "0.25rem 1rem",
722
+ margin: 0,
723
+ padding: 0,
724
+ listStyle: "none",
725
+ fontSize: "0.8rem",
726
+ overflowWrap: "anywhere",
727
+ },
655
728
  verdictMetricValue: {
656
729
  display: "grid",
657
730
  margin: 0,
@@ -990,15 +1063,38 @@ const styles = stylex.create({
990
1063
  judgeContextList: { display: "grid", gap: "0.3rem", margin: 0 },
991
1064
  judgeContextItem: {
992
1065
  display: "grid",
993
- gridTemplateColumns: {
994
- default: "minmax(7.5rem, 0.3fr) minmax(0, 1fr)",
995
- "@media (max-width: 620px)": "1fr",
996
- },
997
- gap: "0.65rem",
1066
+ gap: "0.45rem",
998
1067
  padding: "0.4rem 0.5rem",
999
1068
  borderRadius: 6,
1000
1069
  backgroundColor: "var(--bg)",
1001
1070
  },
1071
+ judgeContextFieldSummary: {
1072
+ display: "flex",
1073
+ alignItems: "baseline",
1074
+ gap: "0.5rem",
1075
+ cursor: "pointer",
1076
+ minWidth: 0,
1077
+ },
1078
+ judgeContextShape: {
1079
+ color: "var(--muted)",
1080
+ fontSize: "0.68rem",
1081
+ fontVariantNumeric: "tabular-nums",
1082
+ },
1083
+ judgeContextFieldBody: {
1084
+ display: "grid",
1085
+ gap: "0.35rem",
1086
+ maxHeight: "24rem",
1087
+ overflow: "auto",
1088
+ },
1089
+ judgeContextStringList: {
1090
+ margin: 0,
1091
+ paddingLeft: "1.1rem",
1092
+ color: "var(--text)",
1093
+ fontSize: "0.72rem",
1094
+ lineHeight: 1.5,
1095
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
1096
+ wordBreak: "break-word",
1097
+ },
1002
1098
  judgeContextLabel: {
1003
1099
  color: "var(--subtle)",
1004
1100
  fontSize: "0.64rem",
@@ -1281,7 +1377,7 @@ function ViewerSidebarContent({ mode, open = true, headingId, navigationId, onTo
1281
1377
  const history = useExecutionHistory();
1282
1378
  const groups = groupTests(catalog.data?.tests ?? []);
1283
1379
  const statuses = latestTestStatuses(history.data ?? []);
1284
- return (_jsxs(_Fragment, { children: [_jsx(SidebarHeader, { mode: mode, open: open, count: catalog.data?.tests.length ?? 0, headingId: headingId, navigationId: navigationId, onToggle: mode === "mobile" ? onClose : onToggle, onNavigate: onClose }), open ? (_jsx(SidebarNavigation, { groups: groups, statuses: statuses, error: catalog.isError, navigationId: navigationId, onNavigate: onClose })) : null] }));
1380
+ return (_jsxs(_Fragment, { children: [_jsx(SidebarHeader, { mode: mode, open: open, count: catalog.data?.tests.length ?? 0, headingId: headingId, navigationId: navigationId, onToggle: mode === "mobile" ? onClose : onToggle, onNavigate: onClose }), open ? (_jsx(SidebarNavigation, { groups: groups, statuses: statuses, catalogState: catalogState(catalog), navigationId: navigationId, onNavigate: onClose })) : null] }));
1285
1381
  }
1286
1382
  function SidebarHeader({ mode, open, count, headingId, navigationId, onToggle, onNavigate, }) {
1287
1383
  const { workers } = useContext(WorkerContext);
@@ -1309,8 +1405,21 @@ function SidebarToggle({ mode, open, navigationId, onToggle, }) {
1309
1405
  const mobile = mode === "mobile";
1310
1406
  return (_jsx("button", { type: "button", "data-drawer-close": mobile ? "true" : undefined, "aria-controls": navigationId, "aria-expanded": open, "aria-label": mobile ? "Close test menu" : open ? "Collapse test sidebar" : "Expand test sidebar", ...stylex.props(styles.buttonReset, styles.toggleButton, mobile && styles.mobileCloseButton), onClick: onToggle, children: mobile ? "×" : open ? "←" : "→" }));
1311
1407
  }
1312
- function SidebarNavigation({ groups, statuses, error, navigationId, onNavigate, }) {
1313
- return (_jsxs(_Fragment, { children: [error ? _jsx("p", { role: "alert", children: "Test discovery is unavailable." }) : null, _jsx("nav", { id: navigationId, "aria-label": "Tests", ...stylex.props(styles.navigation), children: [...groups].map(([name, tests]) => (_jsxs("section", { "aria-label": `${name} tests`, ...stylex.props(styles.navSection), children: [_jsxs("div", { ...stylex.props(styles.groupHeader), children: [_jsx("strong", { ...stylex.props(styles.groupLabel), children: name }), _jsxs("div", { ...stylex.props(styles.groupHeaderActions), children: [_jsx("span", { ...stylex.props(styles.groupCount), children: tests.length }), _jsx(RunTestGroupButton, { name: name, tests: tests, onNavigate: onNavigate })] })] }), tests.map((test) => (_jsx(TestLink, { test: test, status: statuses.get(test.id), onNavigate: onNavigate }, test.id)))] }, name))) })] }));
1408
+ /** A failed refresh over a list already shown is stale, not missing. */
1409
+ function catalogState(catalog) {
1410
+ if (!catalog.isError)
1411
+ return "ready";
1412
+ return catalog.data ? "stale" : "unavailable";
1413
+ }
1414
+ function CatalogNotice({ state }) {
1415
+ if (state === "ready")
1416
+ return null;
1417
+ if (state === "stale")
1418
+ return (_jsx("p", { role: "status", ...stylex.props(styles.catalogNotice), children: "Test list may be out of date. Retrying\u2026" }));
1419
+ return (_jsx("p", { role: "alert", ...stylex.props(styles.catalogNotice, styles.catalogNoticeError), children: "Test discovery is unavailable. Retrying\u2026" }));
1420
+ }
1421
+ function SidebarNavigation({ groups, statuses, catalogState, navigationId, onNavigate, }) {
1422
+ return (_jsxs(_Fragment, { children: [_jsx(CatalogNotice, { state: catalogState }), _jsx("nav", { id: navigationId, "aria-label": "Tests", ...stylex.props(styles.navigation), children: [...groups].map(([name, tests]) => (_jsxs("section", { "aria-label": `${name} tests`, ...stylex.props(styles.navSection), children: [_jsxs("div", { ...stylex.props(styles.groupHeader), children: [_jsx("strong", { title: tests[0] ? testIntent(tests[0], tests).suite : undefined, ...stylex.props(styles.groupLabel), children: name }), _jsxs("div", { ...stylex.props(styles.groupHeaderActions), children: [_jsx("span", { ...stylex.props(styles.groupCount), children: tests.length }), _jsx(RunTestGroupButton, { name: name, tests: tests, onNavigate: onNavigate })] })] }), tests.map((test) => (_jsx(TestLink, { test: test, hint: testIntent(test, tests).own, status: statuses.get(test.id), onNavigate: onNavigate }, test.id)))] }, name))) })] }));
1314
1423
  }
1315
1424
  function RunTestGroupButton({ name, tests, onNavigate, }) {
1316
1425
  const { workers } = useContext(WorkerContext);
@@ -1326,8 +1435,8 @@ function RunTestGroupButton({ name, tests, onNavigate, }) {
1326
1435
  });
1327
1436
  return (_jsx("button", { type: "button", "aria-label": `Run ${name} suite`, title: `Run ${name} suite`, ...stylex.props(styles.buttonReset, styles.groupRunButton), onClick: () => run.mutate(), disabled: run.isPending, children: run.isPending ? "●" : "▶" }));
1328
1437
  }
1329
- function TestLink({ test, status, onNavigate, }) {
1330
- return (_jsxs(Link, { to: "/tests/$testId", params: { testId: test.id }, search: {}, ...stylex.props(styles.navLink), activeProps: stylex.props(styles.navLink, styles.active), onClick: onNavigate, children: [_jsxs("div", { ...stylex.props(styles.navItemHeader), children: [status ? _jsx(TestStatus, { status: status }) : _jsx("span", { "aria-hidden": "true" }), _jsx("span", { ...stylex.props(styles.navItemTitle), children: test.title.at(-1) })] }), test.project !== "default" ? _jsx("div", { ...stylex.props(styles.meta), children: test.project }) : null] }));
1438
+ function TestLink({ test, hint, status, onNavigate, }) {
1439
+ return (_jsxs(Link, { to: "/tests/$testId", params: { testId: test.id }, search: {}, ...stylex.props(styles.navLink), activeProps: stylex.props(styles.navLink, styles.active), onClick: onNavigate, children: [_jsxs("div", { ...stylex.props(styles.navItemHeader), children: [status ? _jsx(TestStatus, { status: status }) : _jsx("span", { "aria-hidden": "true" }), _jsx("span", { title: hint ?? test.title.at(-1), ...stylex.props(styles.navItemTitle), children: test.title.at(-1) })] }), test.project !== "default" ? _jsx("div", { ...stylex.props(styles.meta), children: test.project }) : null] }));
1331
1440
  }
1332
1441
  function TestStatus({ status }) {
1333
1442
  return (_jsx("span", { role: "img", "aria-label": `Latest execution: ${status}`, title: `Latest execution: ${status}`, ...stylex.props(styles.navStatus), children: _jsx("span", { "aria-hidden": "true", ...stylex.props(styles.navStatusDot, status === "passed" && styles.navStatusDotPass, status === "failed" && styles.navStatusDotFail, status === "running" && styles.navStatusDotRunning) }) }));
@@ -1340,9 +1449,9 @@ function TestPage() {
1340
1449
  const test = catalog.data?.tests.find((item) => item.id === testId);
1341
1450
  if (!test)
1342
1451
  return (_jsx("p", { role: "alert", children: "This test is no longer available. Choose another test in the sidebar." }));
1343
- return _jsx(TestDetail, { test: test }, test.id);
1452
+ return (_jsx(TestDetail, { test: test, intent: testIntent(test, catalog.data?.tests ?? []) }, test.id));
1344
1453
  }
1345
- function TestDetail({ test }) {
1454
+ function TestDetail({ test, intent }) {
1346
1455
  const { execution, view } = testRoute.useSearch();
1347
1456
  const history = useExecutionHistory();
1348
1457
  const navigate = useNavigate();
@@ -1359,7 +1468,7 @@ function TestDetail({ test }) {
1359
1468
  params: { testId: test.id },
1360
1469
  search: { execution, view: next === "current" ? undefined : next },
1361
1470
  });
1362
- return (_jsxs(_Fragment, { children: [_jsx(TestHeader, { test: test, onStarted: chooseExecution }), _jsx(BatchRunLink, { run: runs.find((run) => run.id === selected) }), _jsx(TestTabs, { active: activeView, onChange: chooseView }), _jsx(TestViewPanel, { view: activeView, test: test, runs: runs, selected: selected, historyLoading: history.isPending, onChooseExecution: chooseExecution })] }));
1471
+ return (_jsxs(_Fragment, { children: [_jsx(TestHeader, { test: test, intent: intent, onStarted: chooseExecution }), _jsx(BatchRunLink, { run: runs.find((run) => run.id === selected) }), _jsx(TestTabs, { active: activeView, onChange: chooseView }), _jsx(TestViewPanel, { view: activeView, test: test, runs: runs, selected: selected, historyLoading: history.isPending, onChooseExecution: chooseExecution })] }));
1363
1472
  }
1364
1473
  /** Return path to the run inbox when this test was opened from a batch run. */
1365
1474
  function BatchRunLink({ run }) {
@@ -1368,8 +1477,14 @@ function BatchRunLink({ run }) {
1368
1477
  return null;
1369
1478
  return (_jsxs(Link, { to: "/executions/$executionId", params: { executionId: run.id }, ...stylex.props(styles.batchRunLink), children: ["\u2190 Back to run \u00B7 ", formatRunDate(run.startedAt), " \u00B7 ", count, " tests"] }));
1370
1479
  }
1371
- function TestHeader({ test, onStarted }) {
1372
- return (_jsxs("header", { ...stylex.props(styles.hero), children: [_jsxs("div", { ...stylex.props(styles.heroTop), children: [_jsxs("div", { ...stylex.props(styles.heroCopy), children: [_jsx("div", { ...stylex.props(styles.brand), children: test.title.slice(0, -1).join(" / ") }), _jsx("h2", { ...stylex.props(styles.title), children: test.title.at(-1) }), _jsx("p", { ...stylex.props(styles.description), children: test.description ?? "This test has no additional description." })] }), _jsx(RunTestButton, { test: test, onStarted: onStarted })] }), _jsxs("div", { ...stylex.props(styles.path), children: [test.project, " \u00B7 ", test.file] })] }));
1480
+ function TestHeader({ test, intent, onStarted, }) {
1481
+ return (_jsxs("header", { ...stylex.props(styles.hero), children: [_jsxs("div", { ...stylex.props(styles.heroTop), children: [_jsxs("div", { ...stylex.props(styles.heroCopy), children: [_jsx("div", { ...stylex.props(styles.brand), children: test.title.slice(0, -1).join(" / ") }), intent.suite ? (_jsx("p", { title: intent.suite, ...stylex.props(styles.suiteDescription), children: intent.suite })) : null, _jsx("h2", { ...stylex.props(styles.title), children: test.title.at(-1) }), intent.own ? _jsx("p", { ...stylex.props(styles.description), children: intent.own }) : null] }), _jsx(RunTestButton, { test: test, onStarted: onStarted })] }), _jsx(TestChecks, { criteria: test.criteria ?? [] }), _jsxs("div", { ...stylex.props(styles.path), children: [test.project, " \u00B7 ", test.file] })] }));
1482
+ }
1483
+ /** What the test checks, folded: the verdict below lists the same checks once a run exists. */
1484
+ function TestChecks({ criteria }) {
1485
+ if (!criteria.length)
1486
+ return null;
1487
+ return (_jsxs("details", { "data-disclosure": true, ...stylex.props(styles.judgeContextDisclosure), children: [_jsxs("summary", { ...stylex.props(styles.judgeContextSummary), children: ["Passes when \u00B7 ", criteria.length, " ", pluralize(criteria.length, "check")] }), _jsx("ul", { ...stylex.props(styles.criteriaList, styles.headerChecks), children: criteria.map((criterion) => (_jsxs("li", { ...stylex.props(styles.criterion), children: [_jsx("span", { "aria-hidden": "true", ...stylex.props(styles.criterionIcon), children: "\u2022" }), _jsx("span", { children: criterion })] }, criterion))) })] }));
1373
1488
  }
1374
1489
  const TEST_VIEWS = [
1375
1490
  { id: "current", label: "Current run" },
@@ -1491,7 +1606,7 @@ function ExecutionDetailView({ execution, criteria = [], }) {
1491
1606
  });
1492
1607
  const stopping = execution.status === "running" && (cancel.isPending || cancel.isSuccess);
1493
1608
  const stopLabel = (execution.testIds?.length ?? 0) > 1 ? "Stop suite run" : "Stop this run";
1494
- return (_jsxs("div", { ...stylex.props(styles.statusPanel), children: [execution.status === "running" && execution.cancellable ? (_jsx("div", { ...stylex.props(styles.runControls), children: _jsx("button", { type: "button", ...stylex.props(styles.buttonReset, styles.button, styles.secondaryButton), onClick: () => cancel.mutate(), disabled: stopping, children: stopping ? "Stopping…" : stopLabel }) })) : null, cancel.isError ? _jsx("p", { role: "alert", children: "The run could not be stopped." }) : null, _jsxs("section", { ...stylex.props(styles.list), children: [execution.attempts.map((attempt) => (_jsx(AttemptCard, { attempt: attempt, criteria: criteria, showTitle: false }, attempt.id))), execution.attempts.length === 0 ? (_jsx("p", { ...stylex.props(styles.empty), children: emptyExecutionMessage(execution, stopping) })) : null] })] }));
1609
+ return (_jsxs("div", { ...stylex.props(styles.statusPanel), children: [execution.status === "running" && execution.cancellable ? (_jsx("div", { ...stylex.props(styles.runControls), children: _jsx("button", { type: "button", ...stylex.props(styles.buttonReset, styles.button, styles.secondaryButton), onClick: () => cancel.mutate(), disabled: stopping, children: stopping ? "Stopping…" : stopLabel }) })) : null, cancel.isError ? _jsx("p", { role: "alert", children: "The run could not be stopped." }) : null, _jsxs("section", { ...stylex.props(styles.list), children: [execution.attempts.map((attempt) => (_jsx(AttemptCard, { attempt: attempt, criteria: criteria, showTitle: false, progress: execution.progress?.filter((entry) => entry.attemptId === attempt.id) }, attempt.id))), execution.attempts.length === 0 ? (_jsx("p", { ...stylex.props(styles.empty), children: emptyExecutionMessage(execution, stopping) })) : null] })] }));
1495
1610
  }
1496
1611
  function emptyExecutionMessage(execution, stopping) {
1497
1612
  if (stopping)
@@ -1523,11 +1638,45 @@ function ComparisonColumn({ execution, label }) {
1523
1638
  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)))] }));
1524
1639
  }
1525
1640
  /** `showTitle` is off on a test page, whose header already names the test. */
1526
- function AttemptCard({ attempt, criteria = [], showTitle = true, }) {
1641
+ function AttemptCard({ attempt, criteria = [], showTitle = true, progress = [], }) {
1527
1642
  const { operations, output, title } = attemptPresentation(attempt, criteria);
1528
1643
  const agents = operations.filter((operation) => operation.kind !== "evaluation");
1529
1644
  const judges = operations.filter((operation) => operation.kind === "evaluation");
1530
- return (_jsxs("article", { ...stylex.props(styles.attemptCard), children: [_jsxs("div", { ...stylex.props(styles.row), children: [_jsxs("div", { children: [showTitle ? _jsx("strong", { ...stylex.props(styles.attemptTitle), children: title }) : null, _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(TestVerdict, { attempt: attempt, criteria: criteria, operations: operations }), _jsx(OperationList, { operations: operations }), _jsx(ConversationSection, { label: "Judges", sectionOperations: judges, allOperations: operations, attempt: attempt }), _jsx(ConversationSection, { label: "Agents", sectionOperations: agents, allOperations: operations, attempt: attempt }), _jsx(TechnicalDetails, { operations: operations, output: output })] }));
1645
+ return (_jsxs("article", { ...stylex.props(styles.attemptCard), children: [_jsxs("div", { ...stylex.props(styles.row), children: [_jsxs("div", { children: [showTitle ? _jsx("strong", { ...stylex.props(styles.attemptTitle), children: title }) : null, _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 })] })] }), attempt.status === "running" ? (_jsx(LiveRunStrip, { attempt: attempt, operations: operations, progress: progress.at(-1) })) : null, _jsx(TestVerdict, { attempt: attempt, criteria: criteria, operations: operations }), _jsx(OperationList, { operations: operations }), _jsx(ConversationSection, { label: "Judges", sectionOperations: judges, allOperations: operations, attempt: attempt }), _jsx(ConversationSection, { label: "Agents", sectionOperations: agents, allOperations: operations, attempt: attempt }), _jsx(TechnicalDetails, { operations: operations, output: output })] }));
1646
+ }
1647
+ /** Where a long run is right now: how long it has run, how far it got, and what it is doing. */
1648
+ function LiveRunStrip({ attempt, operations, progress, }) {
1649
+ const step = currentStep(operations);
1650
+ const reported = progress ? progressSummary(progress.data) : undefined;
1651
+ return (_jsxs("section", { "aria-label": "Live run status", ...stylex.props(styles.liveStrip), children: [_jsxs("div", { ...stylex.props(styles.liveStripLine), children: [_jsx(RunningIndicator, { kind: step.kind }), attempt.startedAt ? (_jsx("span", { ...stylex.props(styles.liveStripElapsed), children: _jsx(ElapsedTime, { since: attempt.startedAt }) })) : null, _jsxs("span", { ...stylex.props(styles.liveStripMeta), children: [step.count, " ", pluralize(step.count, "step")] })] }), _jsxs("p", { "aria-live": "polite", title: step.detail, ...stylex.props(styles.liveStripNow), children: [_jsx("span", { ...stylex.props(styles.liveStripLabel), children: "Now:" }), " ", step.label, step.detail ? _jsx("code", { ...stylex.props(styles.liveStripDetail), children: step.detail }) : null] }), reported ? (_jsxs("p", { title: reported, ...stylex.props(styles.liveStripNow), children: [_jsx("span", { ...stylex.props(styles.liveStripLabel), children: "Progress:" }), " ", reported] })) : null] }));
1652
+ }
1653
+ /** The newest thing the running test did, read from the same live records as the transcript. */
1654
+ function currentStep(operations) {
1655
+ const count = operations
1656
+ .filter((operation) => operation.kind !== "evaluation")
1657
+ .flatMap((operation) => liveConversationTimeline(operation.data.filter(isRecord)))
1658
+ .filter((event) => event.kind === "tool").length;
1659
+ const active = [...operations].reverse().find((operation) => operation.status === "running");
1660
+ if (!active)
1661
+ return { label: "Preparing the next step", count };
1662
+ if (active.kind === "evaluation")
1663
+ return { kind: active.kind, label: `Judge ${active.name ?? ""} is reviewing`.trim(), count };
1664
+ const latest = liveConversationTimeline(active.data.filter(isRecord)).at(-1);
1665
+ if (latest?.kind === "tool") {
1666
+ const tool = toolPresentation(latest.tool);
1667
+ return { kind: active.kind, label: tool.title, detail: tool.detail || undefined, count };
1668
+ }
1669
+ const label = latest ? "Writing a reply" : "Waiting for the first agent event";
1670
+ return { kind: active.kind, label, count };
1671
+ }
1672
+ /** One line from whatever the test passed to reportProgress. */
1673
+ function progressSummary(data) {
1674
+ if (typeof data === "string")
1675
+ return data || undefined;
1676
+ if (!isRecord(data))
1677
+ return data === undefined || data === null ? undefined : String(data);
1678
+ const facts = Object.entries(data).map(([key, value]) => `${humanizeKey(key)}: ${formatConversationValue(value)}`);
1679
+ return facts.join(" · ") || undefined;
1531
1680
  }
1532
1681
  /** Test-level outcome first: every check with its result, including judge-decided checks. */
1533
1682
  function TestVerdict({ attempt, criteria, operations, }) {
@@ -1538,7 +1687,7 @@ function TestVerdict({ attempt, criteria, operations, }) {
1538
1687
  status: attempt.status,
1539
1688
  errors: attempt.errors,
1540
1689
  });
1541
- return (_jsxs("section", { "aria-label": "Test verdict", ...stylex.props(styles.verdictPanel, attempt.status === "passed" && styles.verdictPass, attempt.status === "failed" && styles.verdictFail), children: [_jsxs("p", { ...stylex.props(styles.verdictHeadline), children: [capitalize(attempt.status), _jsx("span", { ...stylex.props(styles.verdictCount), children: checkSummary(presentations, attempt.status) })] }), attempt.status === "running" ? (_jsx("p", { ...stylex.props(styles.meta), children: "The test is still running." })) : null, _jsx(VerdictMetrics, { operations: operations }), _jsx(VerdictChecks, { presentations: presentations }), _jsx(UnassignedFailures, { errors: attempt.errors, presentations: presentations })] }));
1690
+ return (_jsxs("section", { "aria-label": "Test verdict", ...stylex.props(styles.verdictPanel, attempt.status === "passed" && styles.verdictPass, attempt.status === "failed" && styles.verdictFail), children: [_jsxs("p", { ...stylex.props(styles.verdictHeadline), children: [capitalize(attempt.status), _jsx("span", { ...stylex.props(styles.verdictCount), children: checkSummary(presentations, attempt.status) })] }), _jsx(VerdictMetrics, { operations: operations, reported: attempt.metrics }), _jsx(VerdictChecks, { presentations: presentations }), _jsx(UnassignedFailures, { errors: attempt.errors, presentations: presentations }), _jsx(VerdictAttachments, { attachments: attempt.attachments })] }));
1542
1691
  }
1543
1692
  const SHORT_CHECK_LIST = 3;
1544
1693
  /** Checks needing attention always show; passing checks fold away once the list is long. */
@@ -1567,8 +1716,22 @@ const VERDICT_METRICS = [
1567
1716
  format: (value) => String(Math.round(value)),
1568
1717
  },
1569
1718
  ];
1719
+ /** `p95LatencyMs` → "P95 latency"; numbers keyed `…Ms` read as durations. */
1720
+ function reportedMetricRows(reported) {
1721
+ return Object.entries(reported ?? {}).map(([key, value]) => {
1722
+ const duration = key.endsWith("Ms");
1723
+ const words = (duration ? key.slice(0, -2) : key).replace(/([a-z0-9])([A-Z])/g, "$1 $2");
1724
+ return {
1725
+ key: `reported:${key}`,
1726
+ label: capitalize(words.toLowerCase()),
1727
+ value: duration
1728
+ ? formatDuration(Math.round(value))
1729
+ : value.toLocaleString(undefined, { maximumFractionDigits: 2 }),
1730
+ };
1731
+ });
1732
+ }
1570
1733
  /** Agent totals for the attempt; a median per run when the test made several agent runs. */
1571
- function VerdictMetrics({ operations }) {
1734
+ function VerdictMetrics({ operations, reported, }) {
1572
1735
  const runs = operations
1573
1736
  .filter((operation) => operation.kind !== "evaluation")
1574
1737
  .map((operation) => operationResult([operation]));
@@ -1579,10 +1742,17 @@ function VerdictMetrics({ operations }) {
1579
1742
  const total = values.reduce((sum, value) => sum + value, 0);
1580
1743
  return [{ ...metric, total, median: median(values) ?? total }];
1581
1744
  });
1582
- if (!rows.length)
1745
+ const reportedRows = reportedMetricRows(reported);
1746
+ if (!rows.length && !reportedRows.length)
1583
1747
  return null;
1584
1748
  const repeated = runs.length > 1;
1585
- return (_jsx("dl", { "aria-label": "Run metrics", ...stylex.props(styles.verdictMetrics), children: rows.map((row) => (_jsxs("div", { ...stylex.props(styles.verdictMetric), children: [_jsx("dt", { ...stylex.props(styles.resourceFactLabel), children: row.label }), _jsxs("dd", { ...stylex.props(styles.verdictMetricValue), children: [row.format(row.total), repeated ? (_jsxs("span", { ...stylex.props(styles.verdictMetricMedian), children: ["median ", row.format(row.median), " per run"] })) : null] })] }, row.key))) }));
1749
+ return (_jsxs("dl", { "aria-label": "Run metrics", ...stylex.props(styles.verdictMetrics), children: [rows.map((row) => (_jsxs("div", { ...stylex.props(styles.verdictMetric), children: [_jsx("dt", { ...stylex.props(styles.resourceFactLabel), children: row.label }), _jsxs("dd", { ...stylex.props(styles.verdictMetricValue), children: [row.format(row.total), repeated ? (_jsxs("span", { ...stylex.props(styles.verdictMetricMedian), children: ["median ", row.format(row.median), " per run"] })) : null] })] }, row.key))), reportedRows.map((row) => (_jsxs("div", { ...stylex.props(styles.verdictMetric), children: [_jsx("dt", { ...stylex.props(styles.resourceFactLabel), children: row.label }), _jsx("dd", { ...stylex.props(styles.verdictMetricValue), children: row.value })] }, row.key)))] }));
1750
+ }
1751
+ /** Files the test attached with a path, opened from the viewer server. */
1752
+ function VerdictAttachments({ attachments }) {
1753
+ if (!attachments?.length)
1754
+ return null;
1755
+ return (_jsx("ul", { "aria-label": "Attachments", ...stylex.props(styles.verdictAttachments), children: attachments.map((attachment) => (_jsxs("li", { children: [_jsx("a", { href: attachment.url, target: "_blank", rel: "noreferrer", children: attachment.name }), _jsxs("span", { ...stylex.props(styles.verdictMetricMedian), children: [" ", attachment.contentType] })] }, attachment.url))) }));
1586
1756
  }
1587
1757
  function checkSummary(presentations, status) {
1588
1758
  const total = presentations.length;
@@ -2069,12 +2239,12 @@ function JudgeAsked({ review }) {
2069
2239
  return null;
2070
2240
  return (_jsx("section", { "aria-label": "Judge question", ...stylex.props(styles.judgeQuestion), children: _jsxs("p", { ...stylex.props(styles.judgeQuestionText), children: [_jsx("span", { ...stylex.props(styles.judgeAskedLabel), children: "Asked" }), _jsx("span", { children: review.question })] }) }));
2071
2241
  }
2072
- /** Everything the test sent the judge, folded away: it mostly repeats the agent answers. */
2242
+ /** Everything the test sent the judge, folded away unless a finding failed. */
2073
2243
  function JudgeInputs({ review, operations, }) {
2074
2244
  if (!review.context.length)
2075
2245
  return null;
2076
- // Inputs matter most before findings exist and when a finding failed.
2077
- const inputsFirst = review.outcomes.length === 0 || review.outcomes.some((outcome) => !outcome.passed);
2246
+ // Inputs explain a failed finding; while the judge runs they only push the transcript away.
2247
+ const inputsFirst = review.outcomes.some((outcome) => !outcome.passed);
2078
2248
  return (_jsx("section", { "aria-label": "Context included", children: _jsxs("details", { "data-disclosure": true, open: inputsFirst, ...stylex.props(styles.judgeContextDisclosure), children: [_jsxs("summary", { ...stylex.props(styles.judgeContextSummary), children: ["What the judge saw \u00B7 ", review.context.length, " selected", " ", pluralize(review.context.length, "field")] }), _jsx(JudgeContext, { context: review.context, operations: operations })] }) }));
2079
2249
  }
2080
2250
  function JudgeContext({ context, operations, }) {
@@ -2082,7 +2252,37 @@ function JudgeContext({ context, operations, }) {
2082
2252
  const [selectedId, setSelectedId] = useState(answers[0]?.id);
2083
2253
  const selected = answers.find((item) => item.id === selectedId) ?? answers[0];
2084
2254
  const panelId = useId();
2085
- return (_jsxs("div", { ...stylex.props(styles.judgeContext), children: [_jsx("p", { ...stylex.props(styles.judgeContextNote), children: "Only these fields, chosen by the test, were sent to the 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] }));
2255
+ return (_jsxs("div", { ...stylex.props(styles.judgeContext), children: [_jsx("p", { ...stylex.props(styles.judgeContextNote), children: "Only these fields, chosen by the test, were sent to the 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("div", { ...stylex.props(styles.judgeContextList), children: supplied.map((item) => (_jsx(JudgeContextField, { item: item }, item.label))) })] })) : null] }));
2256
+ }
2257
+ /** One supplied judge input. A short value reads inline; anything larger folds behind its size. */
2258
+ function JudgeContextField({ item }) {
2259
+ if (isInlineValue(item.rawValue))
2260
+ return (_jsxs("div", { ...stylex.props(styles.judgeContextItem, styles.judgeContextFieldSummary), children: [_jsx("span", { ...stylex.props(styles.judgeContextLabel), children: item.label }), _jsx("span", { ...stylex.props(styles.judgeContextValue), children: item.value })] }));
2261
+ return (_jsxs("details", { "data-disclosure": true, ...stylex.props(styles.judgeContextItem), children: [_jsxs("summary", { ...stylex.props(styles.judgeContextFieldSummary), children: [_jsx("span", { ...stylex.props(styles.judgeContextLabel), children: item.label }), _jsx("span", { ...stylex.props(styles.judgeContextShape), children: describeShape(item.rawValue) })] }), _jsx(JudgeContextFieldValue, { item: item })] }));
2262
+ }
2263
+ function JudgeContextFieldValue({ item }) {
2264
+ const tools = judgeInputToolCalls(item.rawValue);
2265
+ if (tools)
2266
+ return (_jsx("div", { ...stylex.props(styles.judgeContextFieldBody), children: tools.map((tool, index) => (_jsx(ToolActivity, { tool: tool }, tool.seq ?? index))) }));
2267
+ if (isStringList(item.rawValue))
2268
+ return (_jsx("ul", { ...stylex.props(styles.judgeContextFieldBody, styles.judgeContextStringList), children: item.rawValue.map((value, index) => (_jsx("li", { children: value }, `${index}:${value}`))) }));
2269
+ return (_jsx("div", { ...stylex.props(styles.judgeContextFieldBody, styles.judgeContextValue), children: item.value }));
2270
+ }
2271
+ /** A judge input that is a list of agent tool calls reads like the transcript, not as JSON. */
2272
+ function judgeInputToolCalls(value) {
2273
+ if (!Array.isArray(value) || value.length === 0)
2274
+ return undefined;
2275
+ const tools = toolCallsFromOperation({ trace: { toolCalls: value } });
2276
+ return tools.length === value.length ? tools : undefined;
2277
+ }
2278
+ const INLINE_VALUE_LENGTH = 120;
2279
+ function isInlineValue(value) {
2280
+ if (typeof value === "string")
2281
+ return value.length <= INLINE_VALUE_LENGTH && !value.includes("\n");
2282
+ return value === null || value === undefined || typeof value !== "object";
2283
+ }
2284
+ function isStringList(value) {
2285
+ return (Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string"));
2086
2286
  }
2087
2287
  function judgeContextGroups(context, operations) {
2088
2288
  const candidates = operations