reeval 0.10.0 → 0.12.0
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.
- package/README.md +64 -39
- package/THIRD_PARTY_NOTICES.md +75 -106
- package/bin/mcp-timer.cjs +224 -0
- package/dist/{adapter-DWlpOtRn.js → adapter-DigeVFWC.js} +3 -3
- package/dist/{adapter-DWlpOtRn.js.map → adapter-DigeVFWC.js.map} +1 -1
- package/dist/{agent-CaS_2Sce.js → agent-CatUEo5h.js} +21 -6
- package/dist/{agent-CaS_2Sce.js.map → agent-CatUEo5h.js.map} +1 -1
- package/dist/{assemble-B9Alds-z.js → assemble-CF063ms6.js} +19 -17
- package/dist/assemble-CF063ms6.js.map +1 -0
- package/dist/assemble-CJoD_Rzr.js +1 -0
- package/dist/bridge/index.mjs +15 -7
- package/dist/{capture-CNb9SuU5.js → capture-BiD7lVaM.js} +26 -18
- package/dist/{capture-CNb9SuU5.js.map → capture-BiD7lVaM.js.map} +1 -1
- package/dist/capture-Dp3q7K7Y.js +1 -0
- package/dist/{cli-D303Vj5N.js → cli-B5zQCR2r.js} +266 -547
- package/dist/cli-B5zQCR2r.js.map +1 -0
- package/dist/{cli-spec-BPZbQG2p.js → cli-spec-BXIBUlaK.js} +46 -18
- package/dist/cli-spec-BXIBUlaK.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{command-BT9a37ZR.js → command-BMHXHqMT.js} +17 -14
- package/dist/command-BMHXHqMT.js.map +1 -0
- package/dist/command-C94CQNh8.js +91 -0
- package/dist/command-C94CQNh8.js.map +1 -0
- package/dist/command-CVtgyqlM.js +335 -0
- package/dist/command-CVtgyqlM.js.map +1 -0
- package/dist/{command-BLfT7vnQ.js → command-CaZgCTIw.js} +13 -17
- package/dist/command-CaZgCTIw.js.map +1 -0
- package/dist/{command-CMJtHKRa.js → command-DTgBZR0k.js} +6 -6
- package/dist/{command-CMJtHKRa.js.map → command-DTgBZR0k.js.map} +1 -1
- package/dist/{commands-1p-bIWhN.js → commands-BXzIDB3X.js} +1 -1
- package/dist/commands-CjpGBUWr.js +666 -0
- package/dist/commands-CjpGBUWr.js.map +1 -0
- package/dist/{config-DEgnUiU-.js → config-Dv5MG7Rp.js} +31 -34
- package/dist/config-Dv5MG7Rp.js.map +1 -0
- package/dist/{context-C1mxXh80.js → context-Dgn48BqT.js} +13 -2
- package/dist/context-Dgn48BqT.js.map +1 -0
- package/dist/contrasts-CcoMLC_y.js +101 -0
- package/dist/contrasts-CcoMLC_y.js.map +1 -0
- package/dist/db-BeqRiWLl.js +602 -0
- package/dist/db-BeqRiWLl.js.map +1 -0
- package/dist/db-CJDRVQal.js +2 -0
- package/dist/{dist-B8hJImYt.js → dist-CMjp3qqF.js} +81 -14
- package/dist/dist-CMjp3qqF.js.map +1 -0
- package/dist/{docker-sZrNrr82.js → docker-BbTlMmYi.js} +2 -2
- package/dist/{docker-sZrNrr82.js.map → docker-BbTlMmYi.js.map} +1 -1
- package/dist/{docker-7b_2SaH7.js → docker-CCXdbREk.js} +20 -4
- package/dist/{docker-7b_2SaH7.js.map → docker-CCXdbREk.js.map} +1 -1
- package/dist/{docker-yauK-9X8.js → docker-DC69dKxB.js} +1 -1
- package/dist/{doctor-BYNok4UO.js → doctor-CqttrZcs.js} +56 -22
- package/dist/doctor-CqttrZcs.js.map +1 -0
- package/dist/{egress-C4PZTSSe.js → egress-BfK_O9ce.js} +435 -31
- package/dist/egress-BfK_O9ce.js.map +1 -0
- package/dist/{environment-DNjRk0nU.js → environment-BBz1Yes9.js} +225 -12
- package/dist/environment-BBz1Yes9.js.map +1 -0
- package/dist/{evaluate-D4zeRGBF.js → evaluate-CXQ1o-pd.js} +539 -562
- package/dist/evaluate-CXQ1o-pd.js.map +1 -0
- package/dist/{evaluate-C1UVL21l.d.ts → evaluate-yjZKUexW.d.ts} +2 -2
- package/dist/facts-DtENlf9s.js +898 -0
- package/dist/facts-DtENlf9s.js.map +1 -0
- package/dist/{files-BXUdY8tr.js → files-B-eUpCZ1.js} +30 -7
- package/dist/files-B-eUpCZ1.js.map +1 -0
- package/dist/files-BsQbYz2i.js +65 -0
- package/dist/files-BsQbYz2i.js.map +1 -0
- package/dist/{files-DQmHE7pb.js → files-COHQLEHE.js} +6 -17
- package/dist/files-COHQLEHE.js.map +1 -0
- package/dist/files-DqCBW7i8.js +1 -0
- package/dist/files-Ha-jsQXf.js +1 -0
- package/dist/generate-Cs2TMH_k.js +1647 -0
- package/dist/generate-Cs2TMH_k.js.map +1 -0
- package/dist/{git-DfG-YJ1u.js → git-DhykwgKF.js} +2 -2
- package/dist/{git-DfG-YJ1u.js.map → git-DhykwgKF.js.map} +1 -1
- package/dist/{harness-B8QC9u0O.js → harness-BAjw1fcr.js} +3 -3
- package/dist/{harness-B8QC9u0O.js.map → harness-BAjw1fcr.js.map} +1 -1
- package/dist/harness.d.ts +1 -1
- package/dist/harness.js +1 -1
- package/dist/{header-hK-Z_Po9.js → header-kSmWatcO.js} +10 -3
- package/dist/{header-hK-Z_Po9.js.map → header-kSmWatcO.js.map} +1 -1
- package/dist/{images-0On0cztu.js → images-BmJRuTpm.js} +61 -13
- package/dist/images-BmJRuTpm.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/init-Bwc9byQr.js +543 -0
- package/dist/init-Bwc9byQr.js.map +1 -0
- package/dist/init-D7Y1ny1z.js +1 -0
- package/dist/insights-B2Xmhz9o.js +64 -0
- package/dist/insights-B2Xmhz9o.js.map +1 -0
- package/dist/{invocations-DbGfBAA_.js → invocations-CQTbP092.js} +3 -3
- package/dist/{invocations-DbGfBAA_.js.map → invocations-CQTbP092.js.map} +1 -1
- package/dist/{judge-DW_GDOmB.js → judge-Dt6KW6_O.js} +5 -3
- package/dist/judge-Dt6KW6_O.js.map +1 -0
- package/dist/judge.d.ts +7 -6
- package/dist/judge.js +8 -7
- package/dist/judge.js.map +1 -1
- package/dist/{live-jlfszAbb.js → live-DYd2QEyR.js} +2 -2
- package/dist/{live-jlfszAbb.js.map → live-DYd2QEyR.js.map} +1 -1
- package/dist/{model-h_cDtcRr.js → model-B-mel-Gv.js} +46 -20
- package/dist/model-B-mel-Gv.js.map +1 -0
- package/dist/model-BGKYVpoj.js +1 -0
- package/dist/{model-DaVCY3rb.js → model-C7tSwT7f.js} +2 -2
- package/dist/{model-DaVCY3rb.js.map → model-C7tSwT7f.js.map} +1 -1
- package/dist/{model-oqcDplJG.js → model-CWlAqmbU.js} +150 -46
- package/dist/model-CWlAqmbU.js.map +1 -0
- package/dist/model-DHY-dfoO.js +1 -0
- package/dist/{node-B3ydHo6t.js → node-D0mtjRAO.js} +3 -3
- package/dist/{node-B3ydHo6t.js.map → node-D0mtjRAO.js.map} +1 -1
- package/dist/{options-Bh2W63dK.js → options-CPUW97_7.js} +9 -12
- package/dist/options-CPUW97_7.js.map +1 -0
- package/dist/{pairs-CnInRu-G.js → pairs-DP6XOJby.js} +2 -2
- package/dist/{pairs-CnInRu-G.js.map → pairs-DP6XOJby.js.map} +1 -1
- package/dist/{patch-Zac5aIRM.js → patch-D4oluWzH.js} +2 -2
- package/dist/{patch-Zac5aIRM.js.map → patch-D4oluWzH.js.map} +1 -1
- package/dist/{plan-C9Cy27Ay.js → plan-OkwUcJQ9.js} +31 -12
- package/dist/plan-OkwUcJQ9.js.map +1 -0
- package/dist/plugins-CUWMnSNb.js +654 -0
- package/dist/plugins-CUWMnSNb.js.map +1 -0
- package/dist/{preflight-CPWpQeBI.js → preflight-B6pY89bF.js} +1 -1
- package/dist/{preflight-D7iFPKeK.js → preflight-Bjm7_ylE.js} +14 -10
- package/dist/preflight-Bjm7_ylE.js.map +1 -0
- package/dist/{prepare-doGxaAGR.js → prepare-CQmK3SBn.js} +71 -24
- package/dist/prepare-CQmK3SBn.js.map +1 -0
- package/dist/prepare-DbdgtZKe.js +2 -0
- package/dist/{provenance-DmE9uPVt.js → provenance-B3Y17oCC.js} +2 -2
- package/dist/{provenance-DmE9uPVt.js.map → provenance-B3Y17oCC.js.map} +1 -1
- package/dist/providers-CXN2RFz-.js +1 -0
- package/dist/{providers-tAOD1NQN.js → providers-eoniqK6w.js} +24 -10
- package/dist/providers-eoniqK6w.js.map +1 -0
- package/dist/query-CLRZmWBT.js +139 -0
- package/dist/query-CLRZmWBT.js.map +1 -0
- package/dist/query-CmBDYt0_.js +1 -0
- package/dist/{questions-DjAwMxZ3.js → questions-1XOulevV.js} +6 -6
- package/dist/{questions-DjAwMxZ3.js.map → questions-1XOulevV.js.map} +1 -1
- package/dist/{record-BKmMvZ6O.js → record-kk9M1hj-.js} +8 -4
- package/dist/record-kk9M1hj-.js.map +1 -0
- package/dist/remarks-1521zwfW.js +38 -0
- package/dist/remarks-1521zwfW.js.map +1 -0
- package/dist/remarks-BhAV1H4V.js +1 -0
- package/dist/remarks-DlJCqWRs.js +278 -0
- package/dist/remarks-DlJCqWRs.js.map +1 -0
- package/dist/{remote-HDRt16ew.js → remote-cE2VWfkJ.js} +3 -3
- package/dist/{remote-HDRt16ew.js.map → remote-cE2VWfkJ.js.map} +1 -1
- package/dist/resolve-C4zeccCH.js +724 -0
- package/dist/resolve-C4zeccCH.js.map +1 -0
- package/dist/resolve-D30yZYmB.js +1 -0
- package/dist/{results-DuauQI3Z.js → results-Drtt0uFl.js} +33 -7
- package/dist/results-Drtt0uFl.js.map +1 -0
- package/dist/{resume-BRfa8E_s.js → resume-BEFazUfy.js} +4 -4
- package/dist/{resume-BRfa8E_s.js.map → resume-BEFazUfy.js.map} +1 -1
- package/dist/rules-BFzQqPIm.js +317 -0
- package/dist/rules-BFzQqPIm.js.map +1 -0
- package/dist/run-BeMy92Us.js +88 -0
- package/dist/run-BeMy92Us.js.map +1 -0
- package/dist/{sandbox-D3A8oUb7.js → sandbox-CQaJj7n9.js} +2 -2
- package/dist/{sandbox-D3A8oUb7.js.map → sandbox-CQaJj7n9.js.map} +1 -1
- package/dist/sandbox.d.ts +1 -1
- package/dist/sandbox.js +1 -1
- package/dist/server-V4-2w0l8.js +1 -0
- package/dist/{server-XDleKaF_.js → server-xK0les4f.js} +8 -8
- package/dist/{server-XDleKaF_.js.map → server-xK0les4f.js.map} +1 -1
- package/dist/{shared-DYpIb1BI.js → shared-CA61BuPJ.js} +25 -3
- package/dist/shared-CA61BuPJ.js.map +1 -0
- package/dist/source-BDow_oW8.js +1 -0
- package/dist/{source-C8xumIWj.js → source-Q1hEKbCs.js} +2 -2
- package/dist/{source-C8xumIWj.js.map → source-Q1hEKbCs.js.map} +1 -1
- package/dist/src-B9L9yGKI.js +1 -0
- package/dist/{src-ofUaYbuY.js → src-Bcah6ZkT.js} +1 -1
- package/dist/{src-DvCg0NBs.js → src-BhmaorOU.js} +28 -6
- package/dist/{src-DvCg0NBs.js.map → src-BhmaorOU.js.map} +1 -1
- package/dist/{src-CF17Ug08.js → src-C0CeKiLU.js} +19 -4
- package/dist/{src-CF17Ug08.js.map → src-C0CeKiLU.js.map} +1 -1
- package/dist/src-C0xowZ3C.js +2 -0
- package/dist/src-CHIfIKUu.js +2 -0
- package/dist/src-DLtyz4w5.js +2 -0
- package/dist/src-DPIjmReZ.js +2 -0
- package/dist/{src-C_lPtowT.js → src-DXNh5mTr.js} +1 -1
- package/dist/{src-FGXhkEkH.js → src-DiLMkIr9.js} +142 -13
- package/dist/src-DiLMkIr9.js.map +1 -0
- package/dist/{src-B7treWa8.js → src-LekQrr_S.js} +324 -32
- package/dist/src-LekQrr_S.js.map +1 -0
- package/dist/src-bCnsA6Vp.js +2 -0
- package/dist/{src-u8_ixn_F.js → src-cbCb-MPy.js} +20 -4
- package/dist/src-cbCb-MPy.js.map +1 -0
- package/dist/{src-DvK0EIZx.js → src-qGoFkuJF.js} +55 -6
- package/dist/{src-DvK0EIZx.js.map → src-qGoFkuJF.js.map} +1 -1
- package/dist/{src-jzagbuqH.js → src-ynBVosfX.js} +1 -1
- package/dist/state-BQR_BCm4.js +1 -0
- package/dist/{state-C__9swoG.js → state-BmZ8QVZ8.js} +12 -12
- package/dist/{state-C__9swoG.js.map → state-BmZ8QVZ8.js.map} +1 -1
- package/dist/studio-app/assets/index-CnYJijyI.js +58 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/suite-BaHPmAGy.js +1 -0
- package/dist/suite-C8sw2pox.js +481 -0
- package/dist/suite-C8sw2pox.js.map +1 -0
- package/dist/summary-0oQNGskv.js +837 -0
- package/dist/summary-0oQNGskv.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +2 -2
- package/dist/{text-gEcKjAyp.js → text-CrVy5ioL.js} +18 -2
- package/dist/text-CrVy5ioL.js.map +1 -0
- package/dist/{transcript-BNNANU8A.js → transcript-BTxJpW4R.js} +2 -2
- package/dist/{transcript-BNNANU8A.js.map → transcript-BTxJpW4R.js.map} +1 -1
- package/dist/types-C5vI3g1C.js.map +1 -1
- package/dist/{types-g17bRAzt.d.ts → types-GraZtfHN.d.ts} +858 -39
- package/dist/usage-BDo2q_yu.js +1 -0
- package/dist/{command-Bge8F7Ko.js → validate-BdoXUuL_.js} +35 -383
- package/dist/validate-BdoXUuL_.js.map +1 -0
- package/dist/view/index.d.ts +20 -9
- package/dist/view/index.js +7 -6
- package/dist/views-BoUoX4_A.js +125 -0
- package/dist/views-BoUoX4_A.js.map +1 -0
- package/dist/views-noVQNCUP.js +2 -0
- package/dist/{worker-entry-yOchGYYv.js → worker-entry-D0-GErmU.js} +2 -2
- package/dist/{worker-entry-yOchGYYv.js.map → worker-entry-D0-GErmU.js.map} +1 -1
- package/dist/{workspace-D8sSgpiy.js → workspace-B90YDU_a.js} +1 -1
- package/dist/{workspace-CgyHEM0l.js → workspace-tjZFkjxC.js} +4 -4
- package/dist/{workspace-CgyHEM0l.js.map → workspace-tjZFkjxC.js.map} +1 -1
- package/package.json +10 -8
- package/skills/eval-rubric/SKILL.md +130 -0
- package/dist/agent-CURAwx_q.js +0 -334
- package/dist/agent-CURAwx_q.js.map +0 -1
- package/dist/assemble-B9Alds-z.js.map +0 -1
- package/dist/assemble-J5MukZFY.js +0 -1
- package/dist/capture-jquXZBKK.js +0 -1
- package/dist/cli-D303Vj5N.js.map +0 -1
- package/dist/cli-spec-BPZbQG2p.js.map +0 -1
- package/dist/command-BLfT7vnQ.js.map +0 -1
- package/dist/command-BT9a37ZR.js.map +0 -1
- package/dist/command-Bge8F7Ko.js.map +0 -1
- package/dist/command-BkFjijN2.js +0 -1
- package/dist/commands-BFx16WB8.js +0 -225
- package/dist/commands-BFx16WB8.js.map +0 -1
- package/dist/config-DEgnUiU-.js.map +0 -1
- package/dist/context-C1mxXh80.js.map +0 -1
- package/dist/db-CdHizc4_.js +0 -1406
- package/dist/db-CdHizc4_.js.map +0 -1
- package/dist/db-Dm1cRJJP.js +0 -2
- package/dist/dist-B8hJImYt.js.map +0 -1
- package/dist/doctor-BYNok4UO.js.map +0 -1
- package/dist/duration-CEOQww97.js +0 -26
- package/dist/duration-CEOQww97.js.map +0 -1
- package/dist/egress-C4PZTSSe.js.map +0 -1
- package/dist/environment-DNjRk0nU.js.map +0 -1
- package/dist/evaluate-D4zeRGBF.js.map +0 -1
- package/dist/family-D2b7WD10.js +0 -67
- package/dist/family-D2b7WD10.js.map +0 -1
- package/dist/files-BXUdY8tr.js.map +0 -1
- package/dist/files-BhyltlDB.js +0 -1
- package/dist/files-DQmHE7pb.js.map +0 -1
- package/dist/files-ZZPqfAo6.js +0 -1
- package/dist/images-0On0cztu.js.map +0 -1
- package/dist/init-B77Rpcqc.js +0 -360
- package/dist/init-B77Rpcqc.js.map +0 -1
- package/dist/init-BrOWin3W.js +0 -1
- package/dist/judge-DW_GDOmB.js.map +0 -1
- package/dist/model-NM3ID0uR.js +0 -1
- package/dist/model-h_cDtcRr.js.map +0 -1
- package/dist/model-nwWJvaUR.js +0 -1
- package/dist/model-oqcDplJG.js.map +0 -1
- package/dist/options-Bh2W63dK.js.map +0 -1
- package/dist/plan-C9Cy27Ay.js.map +0 -1
- package/dist/preflight-D7iFPKeK.js.map +0 -1
- package/dist/prepare-BGjePFQl.js +0 -2
- package/dist/prepare-doGxaAGR.js.map +0 -1
- package/dist/process-CSVO_jsc.js +0 -119
- package/dist/process-CSVO_jsc.js.map +0 -1
- package/dist/providers-gDMko8fl.js +0 -1
- package/dist/providers-tAOD1NQN.js.map +0 -1
- package/dist/query-Bes9Kh6Y.js +0 -91
- package/dist/query-Bes9Kh6Y.js.map +0 -1
- package/dist/query-C_G_svLU.js +0 -1
- package/dist/record-BKmMvZ6O.js.map +0 -1
- package/dist/results-DuauQI3Z.js.map +0 -1
- package/dist/server-C7sg1ndo.js +0 -1
- package/dist/shared-DYpIb1BI.js.map +0 -1
- package/dist/source-fGd0SaKP.js +0 -1
- package/dist/src-B7treWa8.js.map +0 -1
- package/dist/src-DNnG4e4R.js +0 -2
- package/dist/src-DTbrY1hi.js +0 -2
- package/dist/src-DdyJEh2v.js +0 -2
- package/dist/src-Dj9PAroc.js +0 -2
- package/dist/src-Du8H85Qq.js +0 -2
- package/dist/src-FGXhkEkH.js.map +0 -1
- package/dist/src-pJZOrYR9.js +0 -1
- package/dist/src-u8_ixn_F.js.map +0 -1
- package/dist/state-CgjQo29K.js +0 -1
- package/dist/studio-app/assets/index-DrQc2Yxd.js +0 -58
- package/dist/summary-BLAVLySU.js +0 -385
- package/dist/summary-BLAVLySU.js.map +0 -1
- package/dist/summary-DgyjUh_l.js +0 -192
- package/dist/summary-DgyjUh_l.js.map +0 -1
- package/dist/text-gEcKjAyp.js.map +0 -1
- package/dist/tools-GCyNYSc1.js +0 -249
- package/dist/tools-GCyNYSc1.js.map +0 -1
- package/dist/views-BW6oCDUQ.js +0 -2
- package/dist/views-BmQF4UCI.js +0 -225
- package/dist/views-BmQF4UCI.js.map +0 -1
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
import { l as sha256 } from "./ids-Bp6ut7ZD.js";
|
|
2
|
+
import { a as oneLine, i as formatDuration, n as clip, r as fenced } from "./text-CrVy5ioL.js";
|
|
3
|
+
import { n as formatTokens } from "./usage-CleNKkly.js";
|
|
4
|
+
import { D as runtimeLabel, F as checkLabels, R as commandLine, S as languageLabel, V as labelledJudgements, _ as harnessLabel, l as evalMarkOutcome, lt as formatSeconds, st as formatCount, x as isUngatedMiss } from "./model-CWlAqmbU.js";
|
|
5
|
+
import { i as countedOf, l as suiteResult, n as cancelledOf } from "./suite-C8sw2pox.js";
|
|
6
|
+
import { a as fromModel } from "./insights-B2Xmhz9o.js";
|
|
7
|
+
import { o as warnings } from "./state-BmZ8QVZ8.js";
|
|
8
|
+
import { t as colors } from "./colors-QV9dFVhN.js";
|
|
9
|
+
import { r as remarkLines } from "./remarks-1521zwfW.js";
|
|
10
|
+
//#region src/insights/hash.ts
|
|
11
|
+
/** JSON with every object's keys sorted. */
|
|
12
|
+
function canonicalJson(value) {
|
|
13
|
+
return JSON.stringify(value, (_key, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) : v);
|
|
14
|
+
}
|
|
15
|
+
const factsHash = (suite) => `sha256:${sha256(canonicalJson(suite))}`;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/ui/layout.ts
|
|
18
|
+
/** The block for an eval: a pure function of its job and its shape. */
|
|
19
|
+
function layout(job, across, isFamily, isCompare) {
|
|
20
|
+
if (isCompare) return {
|
|
21
|
+
kind: "regressions",
|
|
22
|
+
family: false
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
kind: job === "choose" && across === "none" ? "choose" : across === "change" ? "measure" : across === "harness" ? "harnesses" : across === "both" ? "both" : "results",
|
|
26
|
+
family: isFamily
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const moved = (c) => c.effect === "better" || c.effect === "worse";
|
|
30
|
+
/** A cell the agent failed, or that errored; not one whose only fails the interrupt cancelled. */
|
|
31
|
+
const failedCell = (c) => c.outcome === "fail" && c.failed > cancelledOf(c) || c.outcome === "infra_error";
|
|
32
|
+
/**
|
|
33
|
+
* The insight slots an eval's layout opens: which rows take a sentence (a failing case; an arm
|
|
34
|
+
* that failed or moved; a failing harness), whether it has findings (a choose or finish eval that
|
|
35
|
+
* measures a change), and whether it has named tools (a choose eval). An `other` eval takes only
|
|
36
|
+
* its answer, unless it compares harnesses, which takes a sentence on any job. What the run's
|
|
37
|
+
* interrupt cancelled opens nothing: the agent didn't fail it, and its effects aren't measured.
|
|
38
|
+
*/
|
|
39
|
+
function slotsOf(job, e) {
|
|
40
|
+
const known = job !== "other";
|
|
41
|
+
const measured = e.outcome !== "cancelled";
|
|
42
|
+
const harnesses = e.across === "harness" || e.across === "both";
|
|
43
|
+
const rows = [];
|
|
44
|
+
if (known || harnesses) {
|
|
45
|
+
if (e.cases) {
|
|
46
|
+
for (const k of e.cases) if (k.outcome !== "pass" && k.outcome !== "cancelled") rows.push(k.path);
|
|
47
|
+
for (const p of e.pooled ?? []) {
|
|
48
|
+
const pooledMoved = measured && (p.effect === "better" || p.effect === "worse");
|
|
49
|
+
if (p.outcome === "fail" || pooledMoved) rows.push(p.path);
|
|
50
|
+
}
|
|
51
|
+
} else if (e.across === "harness") {
|
|
52
|
+
for (const c of e.cells ?? []) if (failedCell(c)) rows.push(c.path);
|
|
53
|
+
} else if (e.across === "change" || e.across === "both") {
|
|
54
|
+
for (const c of e.cells ?? []) if (failedCell(c) || measured && moved(c)) rows.push(c.path);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
rows,
|
|
59
|
+
findings: known && e.across === "change" && measured,
|
|
60
|
+
namedTools: job === "choose"
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** The last line of a run's output while no analysis model writes its answers. */
|
|
64
|
+
const NO_MODEL_HINT = "Set analysis.model in reeval.config.ts for answers to what each eval asks.";
|
|
65
|
+
/** The narrowest a row's label column is, so short labels line up under the name. */
|
|
66
|
+
const LABEL = 17;
|
|
67
|
+
const ASSERTION_MAX = 160;
|
|
68
|
+
const CHECK_LABEL_MAX = 48;
|
|
69
|
+
/** A check's label on one line: an expected value can carry line breaks. */
|
|
70
|
+
const checkLabel = (label) => clip(label.replace(/\s*\n\s*/g, " "), CHECK_LABEL_MAX);
|
|
71
|
+
const VERDICT = {
|
|
72
|
+
pass: "pass",
|
|
73
|
+
fail: "fail",
|
|
74
|
+
infra_error: "infra error",
|
|
75
|
+
cancelled: "cancelled",
|
|
76
|
+
skipped: "skipped"
|
|
77
|
+
};
|
|
78
|
+
const EFFECT = {
|
|
79
|
+
better: "helped",
|
|
80
|
+
worse: "made it worse",
|
|
81
|
+
unclear: "can't tell yet",
|
|
82
|
+
same: ""
|
|
83
|
+
};
|
|
84
|
+
const t = (text, paint) => ({
|
|
85
|
+
text,
|
|
86
|
+
paint
|
|
87
|
+
});
|
|
88
|
+
const textOf = (cell) => typeof cell === "string" ? cell : cell.text;
|
|
89
|
+
const show = (cell) => typeof cell === "string" ? cell : cell.paint ? cell.paint(cell.text) : cell.text;
|
|
90
|
+
/**
|
|
91
|
+
* Aligned rows: the first column is as wide as the widest label with its indent (at least
|
|
92
|
+
* `LABEL`), the others as their widest cell, two spaces apart. Free lines print as they are.
|
|
93
|
+
*/
|
|
94
|
+
function table(items, width = LABEL) {
|
|
95
|
+
const rows = items.filter((i) => "cells" in i);
|
|
96
|
+
const first = Math.max(width, ...rows.filter((r) => r.cells.length > 1).map((r) => r.indent + textOf(r.cells[0] ?? "").length));
|
|
97
|
+
const widths = [];
|
|
98
|
+
for (const r of rows) r.cells.forEach((cell, i) => {
|
|
99
|
+
if (i > 0) widths[i] = Math.max(widths[i] ?? 0, textOf(cell).length);
|
|
100
|
+
});
|
|
101
|
+
return items.map((item) => {
|
|
102
|
+
const pad = " ".repeat(item.indent);
|
|
103
|
+
if ("free" in item) return `${pad}${show(item.free)}`;
|
|
104
|
+
return `${pad}${item.cells.map((cell, i) => {
|
|
105
|
+
const width = i === 0 ? first - item.indent : widths[i] ?? 0;
|
|
106
|
+
const fill = " ".repeat(Math.max(0, width - textOf(cell).length));
|
|
107
|
+
return i === item.cells.length - 1 ? show(cell) : `${show(cell)}${fill}`;
|
|
108
|
+
}).join(" ")}`.trimEnd();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const paintOutcome = (c, o) => o === "pass" ? c.green : o === "fail" ? c.red : o === "infra_error" || o === "cancelled" ? c.yellow : c.dim;
|
|
112
|
+
/** A study (every cell `gate: 'none'`) passes whatever it saw, so it never reads `pass`. */
|
|
113
|
+
function verdict(c, e) {
|
|
114
|
+
if (e.todo) return t("todo", c.dim);
|
|
115
|
+
const cells = e.cells ?? [];
|
|
116
|
+
if (e.outcome === "pass" && cells.length && cells.every((x) => !x.gated)) return t("not gated", c.dim);
|
|
117
|
+
return t(VERDICT[e.outcome], paintOutcome(c, e.outcome));
|
|
118
|
+
}
|
|
119
|
+
const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
120
|
+
/**
|
|
121
|
+
* `3 of 4`, with the attempts that didn't count named: `2 of 3 · 1 infra error · 1 skipped`. A
|
|
122
|
+
* cancelled attempt is recorded as a fail, but the agent didn't fail it, so it's named apart.
|
|
123
|
+
*/
|
|
124
|
+
function cellCount(cell) {
|
|
125
|
+
const cancelled = cancelledOf(cell);
|
|
126
|
+
const counted = countedOf(cell);
|
|
127
|
+
const parts = [
|
|
128
|
+
counted > 0 ? `${cell.passed} of ${counted}` : "",
|
|
129
|
+
cell.infraError ? plural(cell.infraError, "infra error") : "",
|
|
130
|
+
cancelled ? `${cancelled} cancelled` : "",
|
|
131
|
+
cell.skipped ? `${cell.skipped} skipped` : ""
|
|
132
|
+
].filter(Boolean);
|
|
133
|
+
if (!parts.length) return "not run";
|
|
134
|
+
return parts.join(" · ");
|
|
135
|
+
}
|
|
136
|
+
const failing = (k) => k.outcome.status === "not run" || k.outcome.failed + k.outcome.errored > 0;
|
|
137
|
+
/** A check's result in a cell: `not run`, `failed` for one try, else `1 of 4 · 2 not run`. */
|
|
138
|
+
function checkCount(k, single) {
|
|
139
|
+
const o = k.outcome;
|
|
140
|
+
if (o.status === "not run") return "not run";
|
|
141
|
+
const answered = o.passed + o.failed + o.errored;
|
|
142
|
+
return [single && answered === 1 ? o.failed ? "failed" : o.errored ? "couldn't check" : "passed" : `${o.passed} of ${answered}`, ...[!single && o.errored ? `${o.errored} couldn't check` : "", o.notRun ? `${o.notRun} not run` : ""].filter(Boolean)].join(" · ");
|
|
143
|
+
}
|
|
144
|
+
/** The check's first failure, less the `label: ` its row already shows. */
|
|
145
|
+
function assertionOf(k) {
|
|
146
|
+
if (k.outcome.status !== "ran" || !k.outcome.assertion) return "";
|
|
147
|
+
const line = k.outcome.assertion.trim().split("\n")[0]?.trim() ?? "";
|
|
148
|
+
return clip(line.startsWith(`${k.label}: `) ? line.slice(k.label.length + 2) : line, ASSERTION_MAX);
|
|
149
|
+
}
|
|
150
|
+
/** A cell passed outright: every counted attempt, every check, nothing errored. */
|
|
151
|
+
const clean = (cell) => cell.outcome === "pass" && cell.failed === 0 && cell.infraError === 0 && cell.errors.length === 0 && !cell.checks.some(failing);
|
|
152
|
+
/** An error no check explains, by kind: a throw in the eval's body reads `error`. */
|
|
153
|
+
const ERROR_KIND = {
|
|
154
|
+
assertion: "error",
|
|
155
|
+
judge: "judge",
|
|
156
|
+
timeout: "timeout",
|
|
157
|
+
aborted: "cancelled",
|
|
158
|
+
infra: "infra error",
|
|
159
|
+
turn: "turn error"
|
|
160
|
+
};
|
|
161
|
+
const errorKind = (err) => ERROR_KIND[err.kind];
|
|
162
|
+
const errorText = (err) => `${clip(err.message, ASSERTION_MAX)}${err.attempts > 1 ? ` (${plural(err.attempts, "try", "tries")})` : ""}`;
|
|
163
|
+
/** The evidence under a failing cell: its failed and not-run checks, its errors, `+N more passed`. */
|
|
164
|
+
function evidence(c, cell, indent, more = true) {
|
|
165
|
+
const single = cell.passed + cell.failed + cell.infraError <= 1;
|
|
166
|
+
const items = [];
|
|
167
|
+
for (const k of cell.checks.filter(failing)) {
|
|
168
|
+
const count = checkCount(k, single);
|
|
169
|
+
items.push({
|
|
170
|
+
indent,
|
|
171
|
+
cells: [checkLabel(k.label), count === "not run" ? t(count, c.dim) : t(count, c.red)]
|
|
172
|
+
});
|
|
173
|
+
const assertion = assertionOf(k);
|
|
174
|
+
if (assertion) items.push({
|
|
175
|
+
indent: indent + 2,
|
|
176
|
+
free: t(assertion, c.dim)
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
for (const err of cell.errors) items.push({
|
|
180
|
+
indent,
|
|
181
|
+
cells: [errorKind(err), t(errorText(err), c.dim)]
|
|
182
|
+
});
|
|
183
|
+
const passed = cell.checks.filter((k) => !failing(k)).length;
|
|
184
|
+
if (more && passed && items.length) items.push({
|
|
185
|
+
indent,
|
|
186
|
+
free: t(`+${passed} more passed`, c.dim)
|
|
187
|
+
});
|
|
188
|
+
return items;
|
|
189
|
+
}
|
|
190
|
+
/** A control with no environment of its own is the baseline the variants change. */
|
|
191
|
+
const armOf = (cell) => cell.variant ?? (cell.environment === "-" ? "baseline" : cell.environment);
|
|
192
|
+
/** Each cell's label: the parts that vary between them (harness, arm, sandbox). */
|
|
193
|
+
function labeller(cells, parts) {
|
|
194
|
+
const vary = parts.filter((p) => {
|
|
195
|
+
const of = (cell) => p === "harness" ? cell.harness : p === "arm" ? armOf(cell) : cell.sandbox;
|
|
196
|
+
return new Set(cells.map(of)).size > 1;
|
|
197
|
+
});
|
|
198
|
+
const used = vary.length ? vary : parts.slice(0, 1);
|
|
199
|
+
return (cell) => used.map((p) => p === "harness" ? cell.harness : p === "arm" ? armOf(cell) : cell.sandbox).join(" · ");
|
|
200
|
+
}
|
|
201
|
+
const effectText = (c, effect) => {
|
|
202
|
+
if (!effect) return t("");
|
|
203
|
+
const paint = effect === "better" ? c.green : effect === "worse" ? c.red : c.yellow;
|
|
204
|
+
return t(EFFECT[effect], paint);
|
|
205
|
+
};
|
|
206
|
+
const signedPoints = (x) => `${x >= 0 ? "+" : "−"}${Math.round(Math.abs(x) * 100)}`;
|
|
207
|
+
/** `-v`: a variant's difference from its baseline and the interval its effect comes from. */
|
|
208
|
+
function statsLine(cell, cells) {
|
|
209
|
+
const base = cells.find((x) => x.key === cell.vs);
|
|
210
|
+
if (!base || !cell.interval) return null;
|
|
211
|
+
const rate = (x) => x.passed + x.failed ? x.passed / (x.passed + x.failed) : 0;
|
|
212
|
+
return `difference ${signedPoints(rate(cell) - rate(base))} points, 95% interval ${signedPoints(cell.interval.low)}…${signedPoints(cell.interval.high)}`;
|
|
213
|
+
}
|
|
214
|
+
/** The checks that failed or didn't run in some cell, a column per cell; the rest pass everywhere. */
|
|
215
|
+
function checkMatrix(c, cells, label, indent, width) {
|
|
216
|
+
const labels = [...new Set(cells.flatMap((x) => x.checks.map((k) => k.label)))];
|
|
217
|
+
const shown = labels.filter((l) => cells.some((x) => x.checks.some((k) => k.label === l && failing(k))));
|
|
218
|
+
if (!shown.length) return [];
|
|
219
|
+
const items = [{
|
|
220
|
+
indent,
|
|
221
|
+
cells: ["check", ...cells.map((x) => t(label(x), c.dim))]
|
|
222
|
+
}];
|
|
223
|
+
for (const l of shown) {
|
|
224
|
+
const row = cells.map((x) => {
|
|
225
|
+
const k = x.checks.find((y) => y.label === l);
|
|
226
|
+
if (!k) return t("–", c.dim);
|
|
227
|
+
const count = checkCount(k, false);
|
|
228
|
+
if (!failing(k)) return t(count);
|
|
229
|
+
return t(count, k.outcome.status === "not run" || !x.gated ? c.dim : c.red);
|
|
230
|
+
});
|
|
231
|
+
items.push({
|
|
232
|
+
indent,
|
|
233
|
+
cells: [checkLabel(l), ...row]
|
|
234
|
+
});
|
|
235
|
+
const assertion = cells.map((x) => x.checks.find((y) => y.label === l)).map((k) => k ? assertionOf(k) : "").find(Boolean);
|
|
236
|
+
if (assertion) items.push({
|
|
237
|
+
indent: indent + 2,
|
|
238
|
+
free: t(assertion, c.dim)
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
const passed = labels.length - shown.length;
|
|
242
|
+
if (passed) items.push({
|
|
243
|
+
indent,
|
|
244
|
+
free: t(`+${passed} more passed`, c.dim)
|
|
245
|
+
});
|
|
246
|
+
return table(items, width);
|
|
247
|
+
}
|
|
248
|
+
/** Tools, agent time and tokens per cell, when each has enough tries and it matters. */
|
|
249
|
+
function effortTable(c, cells, label, indent, failed, width) {
|
|
250
|
+
if (cells.length < 2 || cells.some((x) => !x.effort)) return [];
|
|
251
|
+
if (!failed && !cells.some((x) => x.effortDiffers)) return [];
|
|
252
|
+
const tokens = cells.some((x) => x.effort?.tokens !== null);
|
|
253
|
+
const n = (v, f) => v == null ? "–" : f(v);
|
|
254
|
+
return table([{
|
|
255
|
+
indent,
|
|
256
|
+
cells: [
|
|
257
|
+
"effort",
|
|
258
|
+
t("tools", c.dim),
|
|
259
|
+
t("time", c.dim),
|
|
260
|
+
...tokens ? [t("tokens", c.dim)] : []
|
|
261
|
+
]
|
|
262
|
+
}, ...cells.map((x) => ({
|
|
263
|
+
indent,
|
|
264
|
+
cells: [
|
|
265
|
+
label(x),
|
|
266
|
+
n(x.effort?.tools, formatCount),
|
|
267
|
+
n(x.effort?.agentMs, formatSeconds),
|
|
268
|
+
...tokens ? [n(x.effort?.tokens, formatTokens)] : []
|
|
269
|
+
]
|
|
270
|
+
}))], width);
|
|
271
|
+
}
|
|
272
|
+
const NO_SLOTS = {
|
|
273
|
+
row: () => null,
|
|
274
|
+
named: () => [],
|
|
275
|
+
subject: null
|
|
276
|
+
};
|
|
277
|
+
const triesOf = (cells) => cells.reduce((n, x) => n + countedOf(x), 0);
|
|
278
|
+
/** `named`, then a row per tool with the tries that named it. */
|
|
279
|
+
function namedItems(c, slots, cells, indent) {
|
|
280
|
+
const named = slots.named(cells);
|
|
281
|
+
if (!named.length) return [];
|
|
282
|
+
const of = triesOf(cells);
|
|
283
|
+
return [{
|
|
284
|
+
indent,
|
|
285
|
+
free: t("named", c.dim)
|
|
286
|
+
}, ...named.map((n) => ({
|
|
287
|
+
indent: indent + 2,
|
|
288
|
+
cells: [n.tool, `${n.tries} of ${of}`]
|
|
289
|
+
}))];
|
|
290
|
+
}
|
|
291
|
+
/** The tries that named the subject, over the cells' tries: `0 of 4`; empty with no subject. */
|
|
292
|
+
function subjectTries(slots, cells) {
|
|
293
|
+
if (!slots.subject) return "";
|
|
294
|
+
const subject = slots.subject.toLowerCase();
|
|
295
|
+
return `${slots.named(cells).find((x) => x.tool.toLowerCase() === subject)?.tries ?? 0} of ${triesOf(cells)}`;
|
|
296
|
+
}
|
|
297
|
+
/** Web searches or fetches over the tries, when every try reported a number. */
|
|
298
|
+
function webItems(cells, indent) {
|
|
299
|
+
const row = (name, counts) => counts.length && counts.every((x) => x !== null) ? [{
|
|
300
|
+
indent,
|
|
301
|
+
cells: [name, String(counts.reduce((a, b) => (a ?? 0) + (b ?? 0), 0))]
|
|
302
|
+
}] : [];
|
|
303
|
+
return [...row("searches", cells.flatMap((x) => x.searches)), ...row("fetches", cells.flatMap((x) => x.fetches))];
|
|
304
|
+
}
|
|
305
|
+
/** A choose case that missed: its failed checks, the tools named, its searches, then `+N more passed`. */
|
|
306
|
+
function chooseDetail(c, cells, indent, slots) {
|
|
307
|
+
const checks = cells.flatMap((cell) => evidence(c, cell, indent, false));
|
|
308
|
+
const passed = cells.flatMap((x) => x.checks.filter((k) => !failing(k))).length;
|
|
309
|
+
return [
|
|
310
|
+
...checks,
|
|
311
|
+
...namedItems(c, slots, cells, indent),
|
|
312
|
+
...webItems(cells, indent),
|
|
313
|
+
...passed ? [{
|
|
314
|
+
indent,
|
|
315
|
+
free: t(`+${passed} more passed`, c.dim)
|
|
316
|
+
}] : []
|
|
317
|
+
];
|
|
318
|
+
}
|
|
319
|
+
/** Choose a tool, on a single eval: the subject's count, and on a miss the detail. */
|
|
320
|
+
function chooseBody(c, cells, indent, slots) {
|
|
321
|
+
const items = slots.subject ? [{
|
|
322
|
+
indent,
|
|
323
|
+
cells: [slots.subject, subjectTries(slots, cells)]
|
|
324
|
+
}] : [];
|
|
325
|
+
if (!cells.every(clean)) items.push(...chooseDetail(c, cells, indent, slots));
|
|
326
|
+
return table(items);
|
|
327
|
+
}
|
|
328
|
+
/** A row's sentence, as a line under it. */
|
|
329
|
+
const rowLine = (slots, path, indent) => {
|
|
330
|
+
const text = slots.row(path);
|
|
331
|
+
return text ? [{
|
|
332
|
+
indent,
|
|
333
|
+
free: text
|
|
334
|
+
}] : [];
|
|
335
|
+
};
|
|
336
|
+
/** Measure a change: a row per arm with its effect, the check matrix and the effort table. */
|
|
337
|
+
function measureBody(c, cells, indent, o) {
|
|
338
|
+
const label = labeller(cells, ["arm", "sandbox"]);
|
|
339
|
+
const arms = [{
|
|
340
|
+
indent,
|
|
341
|
+
cells: ["arm", t("pass", c.dim)]
|
|
342
|
+
}];
|
|
343
|
+
for (const cell of cells) {
|
|
344
|
+
const effect = o.effects ? gatedEffect(c, cell) : t("");
|
|
345
|
+
arms.push({
|
|
346
|
+
indent,
|
|
347
|
+
cells: [
|
|
348
|
+
label(cell),
|
|
349
|
+
cellCount(cell),
|
|
350
|
+
...effect.text ? [effect] : []
|
|
351
|
+
]
|
|
352
|
+
});
|
|
353
|
+
arms.push(...rowLine(o.slots, cell.path, indent + 2), ...namedItems(c, o.slots, [cell], indent + 2));
|
|
354
|
+
const stats = o.verbose && o.effects ? statsLine(cell, cells) : null;
|
|
355
|
+
if (stats) arms.push({
|
|
356
|
+
indent: indent + 2,
|
|
357
|
+
free: t(stats, c.dim)
|
|
358
|
+
});
|
|
359
|
+
for (const err of cell.errors) arms.push({
|
|
360
|
+
indent: indent + 2,
|
|
361
|
+
free: t(`${errorKind(err)} ${errorText(err)}`, c.dim)
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
const matrix = o.failed || o.effects;
|
|
365
|
+
const labels = [...cells.map(label), ...matrix ? cells.flatMap((x) => x.checks.filter(failing).map((k) => checkLabel(k.label))) : []];
|
|
366
|
+
const width = Math.max(LABEL, ...labels.map((l) => indent + l.length));
|
|
367
|
+
return [
|
|
368
|
+
...table(arms, width),
|
|
369
|
+
...matrix ? checkMatrix(c, cells, label, indent, width) : [],
|
|
370
|
+
...effortTable(c, cells, label, indent, o.failed, width)
|
|
371
|
+
];
|
|
372
|
+
}
|
|
373
|
+
/** A row per cell (harness, or sandbox), the ones that didn't pass outright with their evidence. */
|
|
374
|
+
function cellRows(c, cells, heading, indent, slots) {
|
|
375
|
+
const label = labeller(cells, heading === "harness" ? ["harness", "sandbox"] : ["sandbox"]);
|
|
376
|
+
const items = [{
|
|
377
|
+
indent,
|
|
378
|
+
cells: [heading, t("pass", c.dim)]
|
|
379
|
+
}];
|
|
380
|
+
for (const cell of cells) {
|
|
381
|
+
items.push({
|
|
382
|
+
indent,
|
|
383
|
+
cells: [label(cell), cellCount(cell)]
|
|
384
|
+
});
|
|
385
|
+
items.push(...rowLine(slots, cell.path, indent + 2));
|
|
386
|
+
if (!clean(cell)) items.push(...namedItems(c, slots, [cell], indent + 2), ...evidence(c, cell, indent + 2));
|
|
387
|
+
}
|
|
388
|
+
return table(items);
|
|
389
|
+
}
|
|
390
|
+
/** Compare harnesses with arms: a heading per harness, its arms as Measure a change under it. */
|
|
391
|
+
function bothBody(c, cells, indent, o) {
|
|
392
|
+
const harnessOf = labeller(cells, ["harness", "sandbox"]);
|
|
393
|
+
const groups = /* @__PURE__ */ new Map();
|
|
394
|
+
for (const cell of cells) groups.set(harnessOf(cell), [...groups.get(harnessOf(cell)) ?? [], cell]);
|
|
395
|
+
return [...groups].flatMap(([harness, group]) => [`${" ".repeat(indent)}${harness}`, ...measureBody(c, group, indent + 2, o)]);
|
|
396
|
+
}
|
|
397
|
+
/** The rows under an eval's head (or a family case's), by what its cells compare. */
|
|
398
|
+
function cellsBody(c, kind, cells, indent, o) {
|
|
399
|
+
if (kind === "measure") return measureBody(c, cells, indent, o);
|
|
400
|
+
if (kind === "harnesses") return cellRows(c, cells, "harness", indent, o.slots);
|
|
401
|
+
if (kind === "both") return bothBody(c, cells, indent, o);
|
|
402
|
+
if (kind === "choose") return chooseBody(c, cells, indent, o.slots);
|
|
403
|
+
if (cells.length > 1) return cellRows(c, cells, "sandbox", indent, o.slots);
|
|
404
|
+
return resultsBody(c, cells[0], indent);
|
|
405
|
+
}
|
|
406
|
+
/** A single eval with one cell: its tries, and on anything but a clean pass, the evidence. */
|
|
407
|
+
function resultsBody(c, cell, indent) {
|
|
408
|
+
if (clean(cell)) return [];
|
|
409
|
+
const items = [];
|
|
410
|
+
if (cell.attempts > 1) items.push({
|
|
411
|
+
indent,
|
|
412
|
+
cells: [plural(cell.attempts, "try", "tries"), cellCount(cell)]
|
|
413
|
+
});
|
|
414
|
+
return table([...items, ...evidence(c, cell, indent)]);
|
|
415
|
+
}
|
|
416
|
+
function questionLines(c, questions, indent) {
|
|
417
|
+
return questions.flatMap((q) => {
|
|
418
|
+
const pad = " ".repeat(indent);
|
|
419
|
+
const head = `${pad}${c.dim("question")} ${clip(q.question, 100)}`;
|
|
420
|
+
if (q.votes === 0 && q.error === null) return [head, `${pad} ${c.dim("no pairs to judge: tries that failed their checks are left out")}`];
|
|
421
|
+
if (q.rates.length === 0 && q.error === null) return [head, `${pad} ${c.dim(`${q.votes} ${q.votes === 1 ? "vote" : "votes"}, every one a tie`)}`];
|
|
422
|
+
const cuts = new Set(q.rates.map((r) => r.cut)).size > 1;
|
|
423
|
+
const rows = table(q.rates.map((r) => ({
|
|
424
|
+
indent: indent + 2,
|
|
425
|
+
cells: [cuts ? `${r.name} (${r.cut})` : r.name, `won ${r.wins} of ${r.n}`]
|
|
426
|
+
})));
|
|
427
|
+
const failed = q.error === null ? [] : [`${pad} ${c.yellow(`the model call failed: ${clip(q.error, 200)}`)}`];
|
|
428
|
+
return [
|
|
429
|
+
head,
|
|
430
|
+
...rows,
|
|
431
|
+
...failed
|
|
432
|
+
];
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/** A cell's counts summed over a family's cases. */
|
|
436
|
+
function pooledCount(cases, key) {
|
|
437
|
+
let passed = 0;
|
|
438
|
+
let counted = 0;
|
|
439
|
+
for (const k of cases) {
|
|
440
|
+
const cell = k.cells.find((x) => x.key === key);
|
|
441
|
+
if (!cell) continue;
|
|
442
|
+
passed += cell.passed;
|
|
443
|
+
counted += cell.passed + cell.failed;
|
|
444
|
+
}
|
|
445
|
+
return counted ? `${passed} of ${counted}` : "not run";
|
|
446
|
+
}
|
|
447
|
+
/** A variant's effect, with the `minLift` its interval's low end fell short of, which fails it. */
|
|
448
|
+
function gatedEffect(c, arm) {
|
|
449
|
+
const effect = effectText(c, arm.effect);
|
|
450
|
+
if (arm.minLift === null || !arm.interval || arm.interval.low >= arm.minLift) return effect;
|
|
451
|
+
const short = `not by minLift ${Math.round(arm.minLift * 100)}`;
|
|
452
|
+
return t(effect.text ? `${effect.text}, ${short}` : short, c.red);
|
|
453
|
+
}
|
|
454
|
+
function pooledStats(p, label) {
|
|
455
|
+
return `${label}: ${p.pairs} paired cases, 95% interval ${signedPoints(p.interval.low)}…${signedPoints(p.interval.high)}`;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* A family: its template, then a row per case. With one cell a case is a row with its count;
|
|
459
|
+
* with more, the case is outer and its cells inner, laid out as a single eval's are, and a
|
|
460
|
+
* pooled `all cases` row sums each cell's counts with its variant's pooled effect. A choose
|
|
461
|
+
* family's case row carries the subject's count instead, and a missed case the tools named.
|
|
462
|
+
*/
|
|
463
|
+
function familyBlock(c, e, kind, verbose, slots) {
|
|
464
|
+
const cases = e.cases ?? [];
|
|
465
|
+
const columns = [...new Map(cases.flatMap((k) => k.cells).map((cell) => [cell.key, cell])).values()];
|
|
466
|
+
const pooled = e.pooled ?? [];
|
|
467
|
+
const pooledOutcome = pooled.some((p) => p.outcome === "fail") ? "fail" : "pass";
|
|
468
|
+
const caseWord = (k) => verdict(c, {
|
|
469
|
+
outcome: k.outcome,
|
|
470
|
+
todo: false,
|
|
471
|
+
cells: k.cells
|
|
472
|
+
});
|
|
473
|
+
const pooledWord = t(VERDICT[pooledOutcome], paintOutcome(c, pooledOutcome));
|
|
474
|
+
const words = [...cases.map(caseWord), ...pooled.length ? [pooledWord] : []];
|
|
475
|
+
const wordWidth = Math.max(...words.map((w) => w.text.length));
|
|
476
|
+
const lead = (word, name) => ({
|
|
477
|
+
text: `${word.text.padEnd(wordWidth)} ${name}`,
|
|
478
|
+
paint: (s) => `${show(word)}${s.slice(word.text.length)}`
|
|
479
|
+
});
|
|
480
|
+
const head = `${" ".repeat(17)}${e.template ?? e.name}`;
|
|
481
|
+
const questions = questionLines(c, e.questions, 2);
|
|
482
|
+
if (kind === "choose") {
|
|
483
|
+
const subject = slots.subject ? [t(slots.subject, c.dim)] : [];
|
|
484
|
+
const items = [{
|
|
485
|
+
indent: 2,
|
|
486
|
+
cells: [
|
|
487
|
+
"case",
|
|
488
|
+
t("pass", c.dim),
|
|
489
|
+
...subject
|
|
490
|
+
]
|
|
491
|
+
}];
|
|
492
|
+
for (const k of cases) {
|
|
493
|
+
const passed = k.cells.reduce((n, x) => n + x.passed, 0);
|
|
494
|
+
const tries = triesOf(k.cells);
|
|
495
|
+
const count = k.cells.length === 1 && k.cells[0] ? cellCount(k.cells[0]) : tries ? `${passed} of ${tries}` : "not run";
|
|
496
|
+
const named = slots.subject && tries ? [subjectTries(slots, k.cells)] : [];
|
|
497
|
+
items.push({
|
|
498
|
+
indent: 2,
|
|
499
|
+
cells: [
|
|
500
|
+
lead(caseWord(k), k.label),
|
|
501
|
+
count,
|
|
502
|
+
...named
|
|
503
|
+
]
|
|
504
|
+
});
|
|
505
|
+
items.push(...rowLine(slots, k.path, 4));
|
|
506
|
+
if (!k.cells.every(clean)) items.push(...chooseDetail(c, k.cells, 4, slots));
|
|
507
|
+
}
|
|
508
|
+
return [
|
|
509
|
+
head,
|
|
510
|
+
...table(items),
|
|
511
|
+
...questions
|
|
512
|
+
];
|
|
513
|
+
}
|
|
514
|
+
if (columns.length <= 1) {
|
|
515
|
+
const items = [];
|
|
516
|
+
for (const k of cases) {
|
|
517
|
+
const cell = k.cells[0];
|
|
518
|
+
items.push({
|
|
519
|
+
indent: 2,
|
|
520
|
+
cells: [lead(caseWord(k), k.label), cell ? cellCount(cell) : ""]
|
|
521
|
+
});
|
|
522
|
+
items.push(...rowLine(slots, k.path, 4));
|
|
523
|
+
if (cell && !clean(cell)) items.push(...evidence(c, cell, 4));
|
|
524
|
+
}
|
|
525
|
+
return [
|
|
526
|
+
head,
|
|
527
|
+
...table(items),
|
|
528
|
+
...questions
|
|
529
|
+
];
|
|
530
|
+
}
|
|
531
|
+
const out = [head];
|
|
532
|
+
for (const k of cases) {
|
|
533
|
+
out.push(` ${show(lead(caseWord(k), k.label))}`);
|
|
534
|
+
const sentence = slots.row(k.path);
|
|
535
|
+
if (sentence) out.push(` ${sentence}`);
|
|
536
|
+
const failed = k.outcome !== "pass";
|
|
537
|
+
out.push(...cellsBody(c, kind, k.cells, 4, {
|
|
538
|
+
failed,
|
|
539
|
+
verbose,
|
|
540
|
+
effects: false,
|
|
541
|
+
slots
|
|
542
|
+
}));
|
|
543
|
+
}
|
|
544
|
+
if (pooled.length) {
|
|
545
|
+
const label = labeller(columns, [
|
|
546
|
+
"harness",
|
|
547
|
+
"arm",
|
|
548
|
+
"sandbox"
|
|
549
|
+
]);
|
|
550
|
+
const byKey = new Map(pooled.map((p) => [p.key, p]));
|
|
551
|
+
const items = [{
|
|
552
|
+
indent: 4,
|
|
553
|
+
cells: [kind === "harnesses" ? "harness" : "arm", t("pass", c.dim)]
|
|
554
|
+
}];
|
|
555
|
+
for (const col of columns) {
|
|
556
|
+
const p = byKey.get(col.key);
|
|
557
|
+
const effect = p ? gatedEffect(c, p) : t("");
|
|
558
|
+
items.push({
|
|
559
|
+
indent: 4,
|
|
560
|
+
cells: [
|
|
561
|
+
label(col),
|
|
562
|
+
pooledCount(cases, col.key),
|
|
563
|
+
...effect.text ? [effect] : []
|
|
564
|
+
]
|
|
565
|
+
});
|
|
566
|
+
if (p) items.push(...rowLine(slots, p.path, 6));
|
|
567
|
+
if (p && verbose) items.push({
|
|
568
|
+
indent: 6,
|
|
569
|
+
free: t(pooledStats(p, label(col)), c.dim)
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
out.push(` ${show(lead(pooledWord, "all cases"))}`, ...table(items));
|
|
573
|
+
}
|
|
574
|
+
return [...out, ...questions];
|
|
575
|
+
}
|
|
576
|
+
/** The slots an eval's insight fills, or none without one. */
|
|
577
|
+
function slotsFor(insight) {
|
|
578
|
+
if (!insight) return NO_SLOTS;
|
|
579
|
+
const rows = new Map((insight.rows ?? []).map((r) => [r.row, oneLine(r.text)]));
|
|
580
|
+
const choose = insight.intent.job === "choose";
|
|
581
|
+
const named = (cells) => {
|
|
582
|
+
if (!choose) return [];
|
|
583
|
+
const counts = /* @__PURE__ */ new Map();
|
|
584
|
+
for (const n of insight.namedTools ?? []) {
|
|
585
|
+
if (!cells.some((cell) => n.attempt.startsWith(`${cell.path}#`))) continue;
|
|
586
|
+
for (const tool of new Set(n.tools.map(oneLine))) counts.set(tool, (counts.get(tool) ?? 0) + 1);
|
|
587
|
+
}
|
|
588
|
+
return [...counts].map(([tool, tries]) => ({
|
|
589
|
+
tool,
|
|
590
|
+
tries
|
|
591
|
+
})).sort((a, b) => b.tries - a.tries || (a.tool < b.tool ? -1 : 1));
|
|
592
|
+
};
|
|
593
|
+
return {
|
|
594
|
+
row: (path) => rows.get(path) ?? null,
|
|
595
|
+
named,
|
|
596
|
+
subject: choose && insight.intent.subject.tool ? oneLine(insight.intent.subject.tool) : null
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
/** One eval's block, laid out as `layout()` says for its job, with its insight's sentences. */
|
|
600
|
+
function evalBlock(c, e, verbose = false, insight = null) {
|
|
601
|
+
const shape = layout(insight?.intent.job ?? "other", e.across, e.family, false);
|
|
602
|
+
const slots = slotsFor(insight);
|
|
603
|
+
const answer = insight?.answer ? [` ${oneLine(insight.answer.text)}`] : [];
|
|
604
|
+
const findings = (insight?.findings ?? []).map((f) => ` ${oneLine(f.text)}`);
|
|
605
|
+
if (shape.family) {
|
|
606
|
+
const [head = "", ...rest] = familyBlock(c, e, shape.kind, verbose, slots);
|
|
607
|
+
return [
|
|
608
|
+
head,
|
|
609
|
+
...answer,
|
|
610
|
+
...rest,
|
|
611
|
+
...findings
|
|
612
|
+
];
|
|
613
|
+
}
|
|
614
|
+
const v = verdict(c, e);
|
|
615
|
+
const name = `${e.group.length ? `${e.group.join(" › ")} › ` : ""}${e.name}`;
|
|
616
|
+
const head = `${show(v)}${" ".repeat(Math.max(2, 17 - v.text.length))}${name}`;
|
|
617
|
+
const cells = e.cells ?? [];
|
|
618
|
+
if (e.todo || e.outcome === "skipped" || !cells.length) return [head];
|
|
619
|
+
const failed = e.outcome !== "pass";
|
|
620
|
+
const body = cellsBody(c, shape.kind, cells, 2, {
|
|
621
|
+
failed,
|
|
622
|
+
verbose,
|
|
623
|
+
effects: true,
|
|
624
|
+
slots
|
|
625
|
+
});
|
|
626
|
+
return [
|
|
627
|
+
head,
|
|
628
|
+
...answer,
|
|
629
|
+
...body,
|
|
630
|
+
...findings,
|
|
631
|
+
...questionLines(c, e.questions, 2)
|
|
632
|
+
];
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* `5 fail · 2 pass · 1 cancelled`: errors outside any eval and fails first, every case counting
|
|
636
|
+
* one, zeros left out.
|
|
637
|
+
*/
|
|
638
|
+
function tallyLine(c, tally) {
|
|
639
|
+
const parts = [
|
|
640
|
+
tally.errors ? c.red(plural(tally.errors, "run error")) : "",
|
|
641
|
+
tally.fail ? c.red(`${tally.fail} fail`) : "",
|
|
642
|
+
tally.pass ? c.green(`${tally.pass} pass`) : "",
|
|
643
|
+
tally.infraError ? c.yellow(plural(tally.infraError, "infra error")) : "",
|
|
644
|
+
tally.cancelled ? c.yellow(`${tally.cancelled} cancelled`) : "",
|
|
645
|
+
tally.skipped ? c.dim(`${tally.skipped} skipped`) : ""
|
|
646
|
+
].filter(Boolean);
|
|
647
|
+
return parts.length ? parts.join(c.dim(" · ")) : "no evals ran";
|
|
648
|
+
}
|
|
649
|
+
/** Every eval's block in file-path order, then source order; a blank line around each longer one. */
|
|
650
|
+
function renderBlocks(suite, opts, insights = null) {
|
|
651
|
+
const c = colors(opts.color);
|
|
652
|
+
const byEval = new Map((insights?.evals ?? []).map((x) => [x.evalId, x]));
|
|
653
|
+
const blocks = suite.files.flatMap((f) => [...f.evals].sort((a, b) => a.order - b.order).map((e) => evalBlock(c, e, opts.verbose, byEval.get(e.path) ?? null)));
|
|
654
|
+
const out = [];
|
|
655
|
+
blocks.forEach((block, i) => {
|
|
656
|
+
const prev = blocks[i - 1];
|
|
657
|
+
if (prev && (prev.length > 1 || block.length > 1)) out.push("");
|
|
658
|
+
out.push(...block);
|
|
659
|
+
});
|
|
660
|
+
return out;
|
|
661
|
+
}
|
|
662
|
+
/** Insights for these facts: null when there are none, they failed, or they were written for other facts. */
|
|
663
|
+
const freshInsights = (suite, insights) => insights && !insights.error && insights.factsHash === factsHash(suite) ? insights : null;
|
|
664
|
+
/** Why a run's insights didn't print, or with `-v` what wrote them. */
|
|
665
|
+
function insightNotes(c, suite, insights, fresh, verbose) {
|
|
666
|
+
if (!insights || !fromModel(insights)) return [];
|
|
667
|
+
if (insights.error) return [c.yellow(`insights failed (${clip(insights.error, 200)})`)];
|
|
668
|
+
if (!fresh) return [c.dim(`insights left out: insights.json was written for other facts than this run's; delete it and run reeval analyze ${suite.runId} --insights`)];
|
|
669
|
+
const skipped = insights.budget?.skipped.length ?? 0;
|
|
670
|
+
const failed = insights.failed ?? [];
|
|
671
|
+
const stopped = insights.stopped;
|
|
672
|
+
const finish = `reeval analyze ${suite.runId} --insights to finish`;
|
|
673
|
+
const budget = [
|
|
674
|
+
...skipped ? [c.yellow(`insights skipped for ${plural(skipped, "eval")}: analysis.maxTokens (${formatTokens(insights.budget?.maxTokens ?? 0)}) reached — raise it in reeval.config.ts, or ${finish}`)] : [],
|
|
675
|
+
...failed[0] ? [c.yellow(`insights failed for ${plural(failed.length, "eval")} (${clip(failed[0].error, 160)}), so they print their facts: ${finish}`)] : [],
|
|
676
|
+
...stopped ? [c.yellow(`insights stopped: ${plural(stopped.skipped.length, "eval")} skipped (${stopped.signal === "SIGTERM" ? "SIGTERM" : "Ctrl-C"}): ${finish}`)] : []
|
|
677
|
+
];
|
|
678
|
+
if (!verbose) return budget;
|
|
679
|
+
const { dropped, regenerated } = insights.validation;
|
|
680
|
+
return [...budget, c.dim([
|
|
681
|
+
`insights by ${insights.model} (${insights.promptVersion})`,
|
|
682
|
+
`${plural(dropped, "sentence")} removed`,
|
|
683
|
+
...regenerated ? [`${regenerated} asked again`] : [],
|
|
684
|
+
`${formatTokens(insights.usage.inputTokens)} in, ${formatTokens(insights.usage.outputTokens)} out${insights.usage.reasoningTokens ? ` (${formatTokens(insights.usage.reasoningTokens)} reasoning)` : ""}`
|
|
685
|
+
].join(" · "))];
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* A run's result: its errors outside any eval, its blocks with their insights, the headline,
|
|
689
|
+
* then the tally and, with no analysis model, the hint, then the remarks. Insights written for
|
|
690
|
+
* other facts are left out with a note; remarks come from the record and history, not a model,
|
|
691
|
+
* so they print either way.
|
|
692
|
+
*/
|
|
693
|
+
function render(suite, insights, opts) {
|
|
694
|
+
const c = colors(opts.color);
|
|
695
|
+
const fresh = freshInsights(suite, insights);
|
|
696
|
+
const blocks = renderBlocks(suite, opts, fresh);
|
|
697
|
+
return [
|
|
698
|
+
...suite.errors.flatMap((error) => [c.red(`error: ${error}`), ""]),
|
|
699
|
+
...blocks,
|
|
700
|
+
...blocks.length ? [""] : [],
|
|
701
|
+
...fresh?.headline ? [oneLine(fresh.headline.text)] : [],
|
|
702
|
+
...insightNotes(c, suite, insights, fresh, opts.verbose),
|
|
703
|
+
...opts.notes ?? [],
|
|
704
|
+
tallyLine(c, suite.tally),
|
|
705
|
+
...opts.hint ? [NO_MODEL_HINT] : [],
|
|
706
|
+
...remarkLines(c, suite, insights, opts.verbose)
|
|
707
|
+
];
|
|
708
|
+
}
|
|
709
|
+
//#endregion
|
|
710
|
+
//#region src/history/summary.ts
|
|
711
|
+
const OUTCOME_MARK = {
|
|
712
|
+
pass: "✓",
|
|
713
|
+
fail: "✗",
|
|
714
|
+
infra_error: "!",
|
|
715
|
+
skipped: "↓"
|
|
716
|
+
};
|
|
717
|
+
/** Enough to read the agent's conclusion; the record and transcript have it whole. */
|
|
718
|
+
const LAST_MESSAGE_LIMIT = 4e3;
|
|
719
|
+
const cell = (s) => s.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
720
|
+
/** `5 fail · 2 pass`, with the run's status when it didn't complete. */
|
|
721
|
+
const runTally = (run) => `${tallyLine(colors(false), suiteResult(run).tally)}${run.status === "complete" ? "" : ` · ${run.status === "budget" ? "budget reached" : run.status}`}`;
|
|
722
|
+
const short = (hash) => hash.replace(/^sha256:/, "").slice(0, 12);
|
|
723
|
+
/**
|
|
724
|
+
* The state a run executed in, as label/value pairs, shared by summary.md and `reeval show`. The
|
|
725
|
+
* commit is where the run executed; the commit that adds its history file comes later.
|
|
726
|
+
*/
|
|
727
|
+
function stateRows(run) {
|
|
728
|
+
const { git, stack: env } = run;
|
|
729
|
+
const rows = [];
|
|
730
|
+
rows.push(["Executed at", git ? `${git.sha.slice(0, 12)}${git.branch ? ` on ${git.branch}` : ""}${git.dirty ? ", dirty" : ", clean"} (the commit that adds this record is later)` : "not a git repository"]);
|
|
731
|
+
if (git?.dirty) rows.push(["Uncommitted", git.worktreePatch ? `worktree.patch, ${git.worktreePatch.bytes} bytes, sha256 ${short(git.worktreePatch.sha256)}` : `not saved: ${git.worktreePatchNote ?? "unknown"}${git.diffHash ? `; sha256 ${short(git.diffHash)}` : ""}`]);
|
|
732
|
+
rows.push(["Lockfiles", run.dependencies.lockfiles.map((l) => `${l.path} ${short(l.sha256)}`).join(", ") || "none found"]);
|
|
733
|
+
rows.push(["Images", env.images.length ? env.images.map((i) => `${i.ref} · id ${short(i.id)} · ${i.repoDigests[0] ?? "no registry digest (built locally)"} · CLI ${i.agentCli ?? "n/a"}${i.dockerfile ? ` · dockerfile ${i.dockerfile.path} ${short(i.dockerfile.sha256)}` : ""}${i.install?.length ? ` · install ${i.install.length} on ${i.base}` : ""}`).join("; ") : "none (fake sandbox)"]);
|
|
734
|
+
rows.push(["Harnesses", env.harnesses.map(harnessLabel).join(", ") || "none"]);
|
|
735
|
+
rows.push(["Environments", run.environments.map((e) => `${e.name} ${short(e.digest)}${e.skills.length ? ` · skills ${e.skills.map((s) => `${s.name}${s.commit ? `@${s.commit.slice(0, 7)}` : ""}`).join(", ")}` : ""}${e.mcp.length ? ` · mcp ${e.mcp.join(", ")}` : ""}${e.secrets.length ? ` · secrets ${e.secrets.join(", ")}` : ""}`).join("; ") || "none"]);
|
|
736
|
+
rows.push(["Judge", `${env.judge.model ?? "none by default"}${env.judge.resolvedModels.length ? ` (served ${env.judge.resolvedModels.join(", ")})` : ""} · threshold ${env.judge.threshold} · version ${env.judge.version}`]);
|
|
737
|
+
rows.push(["Context", env.context.length ? `${env.context.length} file${env.context.length === 1 ? "" : "s"}: ${env.context.map((c) => `${c.path} ${short(c.sha256)}`).join(", ")}` : "no skills, rules or MCP config found in the sandbox"]);
|
|
738
|
+
rows.push(["Packages", Object.entries(env.packages).map(([name, v]) => `${name} ${v}`).join(" · ")]);
|
|
739
|
+
const language = languageLabel(env);
|
|
740
|
+
rows.push(["reeval", `${env.reeval} · ${runtimeLabel(env)}${language ? ` · ${language}` : ""} · ${env.os}/${env.arch}`]);
|
|
741
|
+
rows.push(["Runner", run.runner.kind === "ci" ? `CI (${run.runner.provider})${run.runner.runUrl ? ` ${run.runner.runUrl}` : ""}` : `local · ${run.author}`]);
|
|
742
|
+
return rows;
|
|
743
|
+
}
|
|
744
|
+
/** Markdown for people and agents landing on a run cold. Links are relative to the artifact dir. */
|
|
745
|
+
function renderSummary(run, opts) {
|
|
746
|
+
const lines = [
|
|
747
|
+
`# reeval run ${run.runId}`,
|
|
748
|
+
"",
|
|
749
|
+
`${runTally(run)} · started ${run.startedAt} (${formatDuration(run.durationMs)})`,
|
|
750
|
+
"",
|
|
751
|
+
`Plan: ${run.profile ? `profile ${run.profile.name} (${run.profile.reason}) · ` : ""}${run.plan.evals} evals · ${run.plan.cells} cells · ${run.plan.attempts} attempts${run.budget ? ` · budget ${[run.budget.attempts !== void 0 ? `${run.budget.attempts} attempts` : "", run.budget.duration ?? ""].filter(Boolean).join(", ")}` : ""}`,
|
|
752
|
+
"",
|
|
753
|
+
...run.plan.sandboxWarnings.map((w) => `> **Warning:** ${w.message}\n`),
|
|
754
|
+
"## Results",
|
|
755
|
+
"",
|
|
756
|
+
...fenced(render(suiteResult(run), opts.insights ?? null, {
|
|
757
|
+
color: false,
|
|
758
|
+
verbose: false,
|
|
759
|
+
hint: opts.hint
|
|
760
|
+
}), "text"),
|
|
761
|
+
"",
|
|
762
|
+
"## State",
|
|
763
|
+
"",
|
|
764
|
+
"| | |",
|
|
765
|
+
"|---|---|",
|
|
766
|
+
...stateRows(run).map(([k, v]) => `| ${k} | ${cell(v)} |`),
|
|
767
|
+
"",
|
|
768
|
+
...warnings(run).map((w) => `> **Warning:** ${w}\n`),
|
|
769
|
+
`Record: [${run.runId}.json](../${run.runId}.json) · artifacts: ${run.artifacts}`,
|
|
770
|
+
"",
|
|
771
|
+
"## Attempts"
|
|
772
|
+
];
|
|
773
|
+
const rank = (o) => o === "fail" || o === "infra_error" ? 0 : o === "pass" ? 1 : 2;
|
|
774
|
+
for (const e of [...run.evals].sort((x, y) => rank(x.outcome) - rank(y.outcome))) {
|
|
775
|
+
if (e.outcome === "skipped") continue;
|
|
776
|
+
lines.push("", `### ${OUTCOME_MARK[evalMarkOutcome(e)]} ${e.id}`, "");
|
|
777
|
+
const multi = e.attempts.length > 1;
|
|
778
|
+
const inputs = (e.answers ?? []).filter((a) => a.input);
|
|
779
|
+
for (const a of inputs) lines.push(`- Question: ${cell(`"${clip(a.question, 80)}"`)} · [what the model saw](${a.input?.path})`);
|
|
780
|
+
if (inputs.length) lines.push("");
|
|
781
|
+
const attemptRank = (a) => isUngatedMiss(e, a) ? 3 : rank(a.outcome);
|
|
782
|
+
for (const a of [...e.attempts].sort((x, y) => attemptRank(x) - attemptRank(y))) {
|
|
783
|
+
if (a.outcome === "skipped") continue;
|
|
784
|
+
if (isUngatedMiss(e, a)) {
|
|
785
|
+
if (multi) lines.push(`#### ○ ${a.id} · missed, not gated`, "");
|
|
786
|
+
lines.push("<details><summary>Why it missed</summary>", "");
|
|
787
|
+
attemptDetail(lines, a);
|
|
788
|
+
lines.push("", "</details>");
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (multi) lines.push(`#### ${OUTCOME_MARK[a.outcome]} ${a.id}`, "");
|
|
792
|
+
attemptDetail(lines, a);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
lines.push("");
|
|
796
|
+
return lines.join("\n");
|
|
797
|
+
}
|
|
798
|
+
/** A failed verdict already says why a judge error failed the attempt; its message repeats it. */
|
|
799
|
+
const repeatsVerdict = (a) => a.error?.kind === "judge" && a.judgements.some((j) => j.verdict === "fail");
|
|
800
|
+
function attemptDetail(lines, a) {
|
|
801
|
+
if (a.error && !repeatsVerdict(a)) lines.push(`**${a.error.kind}**`, "", ...fenced(a.error.message.split("\n").slice(0, 20)), "");
|
|
802
|
+
for (const turn of a.turns) {
|
|
803
|
+
lines.push(`- Prompt (${turn.status}, ${formatDuration(turn.durationMs)}): ${clip(cell(turn.prompt), 200)}`);
|
|
804
|
+
if (turn.error) lines.push(` - Error: ${clip(cell(turn.error), 500)}`);
|
|
805
|
+
}
|
|
806
|
+
const t = a.timings;
|
|
807
|
+
lines.push(`- Time: setup ${formatDuration(t.setupMs)} · agent ${formatDuration(t.agentMs)} · checks ${formatDuration(t.checksMs)} · judge ${formatDuration(t.judgeMs)}`);
|
|
808
|
+
const act = a.activity;
|
|
809
|
+
const web = (n, what) => n === null ? `${what} not reported` : `${n} ${what}`;
|
|
810
|
+
lines.push(`- Agent: ${act.fileEdits} file edits · ${act.tools} other tool calls · ${web(act.webSearches, "web searches")} · ${web(act.webFetches, "fetches")} · ${formatDuration(act.reasoningMs)} reasoning${act.stalledTools ? ` · ${act.stalledTools} stalled tool${act.stalledTools === 1 ? "" : "s"} (≥2m)${act.maxToolMs ? `, longest ${formatDuration(act.maxToolMs)}` : ""}` : ""}`);
|
|
811
|
+
for (const { judgement: j, label, labelled } of labelledJudgements(a)) {
|
|
812
|
+
const retried = j.retries?.length ? `, after ${j.retries.length} ${j.retries.length === 1 ? "retry" : "retries"}` : "";
|
|
813
|
+
lines.push(`- Judge${labelled ? ` ${cell(label)}` : ""} **${j.verdict}** ${j.score.toFixed(2)} (threshold ${j.threshold}, ${j.model}${retried}): ${j.question}`);
|
|
814
|
+
}
|
|
815
|
+
const labels = checkLabels(a);
|
|
816
|
+
a.checks.forEach((c, i) => {
|
|
817
|
+
const label = labels[i];
|
|
818
|
+
lines.push(`- Check${label ? ` ${cell(label)}` : ""} exit ${c.exitCode}: \`${cell(commandLine(c.command))}\`${c.log ? ` ([log](${c.log.path}))` : ""}`);
|
|
819
|
+
});
|
|
820
|
+
if (a.changedFiles.length) lines.push(`- Changed: ${a.changedFiles.map((f) => f.count ? `\`${f.path}\` (${f.count} files)` : `${f.status} \`${f.path}\``).join(", ")}`);
|
|
821
|
+
const last = a.turns.at(-1)?.text;
|
|
822
|
+
if (last) {
|
|
823
|
+
const shown = last.length > LAST_MESSAGE_LIMIT ? `${last.slice(0, LAST_MESSAGE_LIMIT)}…` : last;
|
|
824
|
+
lines.push("", "Last message:", "", ...shown.trimEnd().split("\n").map((l) => `> ${l}`), "");
|
|
825
|
+
}
|
|
826
|
+
const links = [
|
|
827
|
+
a.files.setupLog && `[setup log](${a.files.setupLog.path})`,
|
|
828
|
+
a.files.transcript && `[transcript](${a.files.transcript.path})`,
|
|
829
|
+
a.files.diff && `[diff](${a.files.diff.path})`,
|
|
830
|
+
a.files.judgeInput && `[judge input](${a.files.judgeInput.path})`
|
|
831
|
+
].filter(Boolean);
|
|
832
|
+
if (links.length) lines.push(`- Files: ${links.join(" · ")}`);
|
|
833
|
+
}
|
|
834
|
+
//#endregion
|
|
835
|
+
export { t as _, assertionOf as a, errorKind as c, labeller as d, plural as f, slotsOf as g, show as h, stateRows as i, errorText as l, renderBlocks as m, renderSummary as n, checkCount as o, render as p, runTally as r, checkLabel as s, OUTCOME_MARK as t, evalBlock as u, table as v, factsHash as y };
|
|
836
|
+
|
|
837
|
+
//# sourceMappingURL=summary-0oQNGskv.js.map
|